Merge changes Id15887ff,I78262baa into rvc-dev

* changes:
  AF: Clear the existing response for the partition on a new request.
  AF: Fix bug where Session#setViewStatesLocked doesn't clear response.
diff --git a/api/test-current.txt b/api/test-current.txt
index 0ca8b2d..4eeaaf87 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1513,6 +1513,10 @@
     field public static final String SAMPLE_RATE = "android.media.audiotrack.sampleRate";
   }
 
+  public final class MediaCas implements java.lang.AutoCloseable {
+    method public void forceResourceLost();
+  }
+
   public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint {
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size);
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size);
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index 4bce89f..482d66f 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -370,13 +370,6 @@
     return processor;
 }
 
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) {
-    AttributionNodeInternal attribution;
-    attribution.set_uid(uid);
-    attribution.set_tag(tag);
-    return attribution;
-}
-
 void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
   std::sort(events->begin(), events->end(),
             [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h
index 6199fa9..c5fcf7c 100644
--- a/cmds/statsd/benchmark/metric_util.h
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -120,9 +120,6 @@
                                              const vector<string>& attributionTags,
                                              const string& name);
 
-// Helper function to create an AttributionNodeInternal proto.
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag);
-
 // Create a statsd log event processor upon the start time in seconds, config and key.
 sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
                                               const ConfigKey& key);
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index a6ae389..0ec11f9 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -81,74 +81,6 @@
       mLogPid(pid) {
 }
 
-LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs) {
-    mLogdTimestampNs = wallClockTimestampNs;
-    mElapsedTimestampNs = elapsedTimestampNs;
-    mTagId = tagId;
-    mLogUid = 0;
-    mContext = create_android_logger(1937006964); // the event tag shared by all stats logs
-    if (mContext) {
-        android_log_write_int64(mContext, elapsedTimestampNs);
-        android_log_write_int32(mContext, tagId);
-    }
-}
-
-LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
-                   int32_t uid,
-                   const std::map<int32_t, int32_t>& int_map,
-                   const std::map<int32_t, int64_t>& long_map,
-                   const std::map<int32_t, std::string>& string_map,
-                   const std::map<int32_t, float>& float_map) {
-    mLogdTimestampNs = wallClockTimestampNs;
-    mElapsedTimestampNs = elapsedTimestampNs;
-    mTagId = util::KEY_VALUE_PAIRS_ATOM;
-    mLogUid = uid;
-
-    int pos[] = {1, 1, 1};
-
-    mValues.push_back(FieldValue(Field(mTagId, pos, 0 /* depth */), Value(uid)));
-    pos[0]++;
-    for (const auto&itr : int_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 2;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-
-    for (const auto&itr : long_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 3;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-
-    for (const auto&itr : string_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 4;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-
-    for (const auto&itr : float_map) {
-        pos[2] = 1;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
-        pos[2] = 5;
-        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
-        mValues.back().mField.decorateLastPos(2);
-        pos[1]++;
-    }
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-        mValues.at(mValues.size() - 2).mField.decorateLastPos(1);
-    }
-}
-
 LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging,
                    bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state,
                    const std::vector<uint8_t>& experimentIds, int32_t userId) {
@@ -184,17 +116,6 @@
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
 }
 
-LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) {
-    mLogdTimestampNs = timestampNs;
-    mTagId = tagId;
-    mLogUid = uid;
-    mContext = create_android_logger(1937006964); // the event tag shared by all stats logs
-    if (mContext) {
-        android_log_write_int64(mContext, timestampNs);
-        android_log_write_int32(mContext, tagId);
-    }
-}
-
 LogEvent::~LogEvent() {
     if (mContext) {
         // This is for the case when LogEvent is created using the test interface
@@ -203,154 +124,6 @@
     }
 }
 
-bool LogEvent::write(int32_t value) {
-    if (mContext) {
-        return android_log_write_int32(mContext, value) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::write(uint32_t value) {
-    if (mContext) {
-        return android_log_write_int32(mContext, value) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::write(int64_t value) {
-    if (mContext) {
-        return android_log_write_int64(mContext, value) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::write(uint64_t value) {
-    if (mContext) {
-        return android_log_write_int64(mContext, value) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::write(const string& value) {
-    if (mContext) {
-        return android_log_write_string8_len(mContext, value.c_str(), value.length()) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::write(float value) {
-    if (mContext) {
-        return android_log_write_float32(mContext, value) >= 0;
-    }
-    return false;
-}
-
-bool LogEvent::writeBytes(const string& value) {
-    /* if (mContext) {
-        return android_log_write_char_array(mContext, value.c_str(), value.length()) >= 0;
-    }*/
-    return false;
-}
-
-bool LogEvent::writeKeyValuePairs(int32_t uid,
-                                  const std::map<int32_t, int32_t>& int_map,
-                                  const std::map<int32_t, int64_t>& long_map,
-                                  const std::map<int32_t, std::string>& string_map,
-                                  const std::map<int32_t, float>& float_map) {
-    if (mContext) {
-         if (android_log_write_list_begin(mContext) < 0) {
-            return false;
-         }
-         write(uid);
-         for (const auto& itr : int_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second);
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         for (const auto& itr : long_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second);
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         for (const auto& itr : string_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second.c_str());
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         for (const auto& itr : float_map) {
-             if (android_log_write_list_begin(mContext) < 0) {
-                return false;
-             }
-             write(itr.first);
-             write(itr.second);
-             if (android_log_write_list_end(mContext) < 0) {
-                return false;
-             }
-         }
-
-         if (android_log_write_list_end(mContext) < 0) {
-            return false;
-         }
-         return true;
-    }
-    return false;
-}
-
-bool LogEvent::write(const std::vector<AttributionNodeInternal>& nodes) {
-    if (mContext) {
-         if (android_log_write_list_begin(mContext) < 0) {
-            return false;
-         }
-         for (size_t i = 0; i < nodes.size(); ++i) {
-             if (!write(nodes[i])) {
-                return false;
-             }
-         }
-         if (android_log_write_list_end(mContext) < 0) {
-            return false;
-         }
-         return true;
-    }
-    return false;
-}
-
-bool LogEvent::write(const AttributionNodeInternal& node) {
-    if (mContext) {
-         if (android_log_write_list_begin(mContext) < 0) {
-            return false;
-         }
-         if (android_log_write_int32(mContext, node.uid()) < 0) {
-            return false;
-         }
-         if (android_log_write_string8(mContext, node.tag().c_str()) < 0) {
-            return false;
-         }
-         if (android_log_write_list_end(mContext) < 0) {
-            return false;
-         }
-         return true;
-    }
-    return false;
-}
-
 void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     int32_t value = readNextValue<int32_t>();
     addToValues(pos, depth, value, last);
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 0a89be4..6c6aab1 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -28,27 +28,6 @@
 namespace os {
 namespace statsd {
 
-struct AttributionNodeInternal {
-    void set_uid(int32_t id) {
-        mUid = id;
-    }
-
-    void set_tag(const std::string& value) {
-        mTag = value;
-    }
-
-    int32_t uid() const {
-        return mUid;
-    }
-
-    const std::string& tag() const {
-        return mTag;
-    }
-
-    int32_t mUid;
-    std::string mTag;
-};
-
 struct InstallTrainInfo {
     int64_t trainVersionCode;
     std::string trainName;
@@ -83,28 +62,6 @@
      */
     bool parseBuffer(uint8_t* buf, size_t len);
 
-    // TODO(b/149590301): delete unused functions below once LogEvent uses the
-    // new socket schema within test code. Really we would like the only entry
-    // points into LogEvent to be the above constructor and parseBuffer functions.
-
-    /**
-     * Constructs a LogEvent with synthetic data for testing. Must call init() before reading.
-     */
-    explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs);
-
-    // For testing. The timestamp is used as both elapsed real time and logd timestamp.
-    explicit LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid);
-
-    /**
-     * Constructs a KeyValuePairsAtom LogEvent from value maps.
-     */
-    explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
-                      int32_t uid,
-                      const std::map<int32_t, int32_t>& int_map,
-                      const std::map<int32_t, int64_t>& long_map,
-                      const std::map<int32_t, std::string>& string_map,
-                      const std::map<int32_t, float>& float_map);
-
     // Constructs a BinaryPushStateChanged LogEvent from API call.
     explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging,
                       bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state,
@@ -152,25 +109,6 @@
     std::vector<uint8_t> GetStorage(size_t key, status_t* err) const;
 
     /**
-     * Write test data to the LogEvent. This can only be used when the LogEvent is constructed
-     * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it.
-     */
-    bool write(uint32_t value);
-    bool write(int32_t value);
-    bool write(uint64_t value);
-    bool write(int64_t value);
-    bool write(const std::string& value);
-    bool write(float value);
-    bool write(const std::vector<AttributionNodeInternal>& nodes);
-    bool write(const AttributionNodeInternal& node);
-    bool writeBytes(const std::string& value);
-    bool writeKeyValuePairs(int32_t uid,
-                            const std::map<int32_t, int32_t>& int_map,
-                            const std::map<int32_t, int64_t>& long_map,
-                            const std::map<int32_t, std::string>& string_map,
-                            const std::map<int32_t, float>& float_map);
-
-    /**
      * Return a string representation of this event.
      */
     std::string ToString() const;
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 2f81c2d..7d765d3 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -1060,13 +1060,6 @@
     return processor;
 }
 
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) {
-    AttributionNodeInternal attribution;
-    attribution.set_uid(uid);
-    attribution.set_tag(tag);
-    return attribution;
-}
-
 void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
   std::sort(events->begin(), events->end(),
             [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 715ba2b..f24705a 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -291,9 +291,6 @@
                                                          const bool usingAlertWindow,
                                                          const OverlayStateChanged::State state);
 
-// Helper function to create an AttributionNodeInternal proto.
-AttributionNodeInternal CreateAttribution(const int& uid, const string& tag);
-
 // Create a statsd log event processor upon the start time in seconds, config and key.
 sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
                                               const StatsdConfig& config, const ConfigKey& key,
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 27c9cfc..aa290404 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -25,6 +25,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutQueryWrapper;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IShortcutChangeCallback;
 import android.content.pm.PackageInstaller;
@@ -67,9 +68,8 @@
     LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName,
             in UserHandle user);
 
-    ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
-            in List shortcutIds, in List<LocusId> locusIds, in ComponentName componentName,
-            int flags, in UserHandle user);
+    ParceledListSlice getShortcuts(String callingPackage, in ShortcutQueryWrapper query,
+            in UserHandle user);
     void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
             in UserHandle user);
     boolean startShortcut(String callingPackage, String packageName, String featureId, String id,
@@ -93,9 +93,8 @@
             in IPackageInstallerCallback callback);
     ParceledListSlice getAllSessions(String callingPackage);
 
-    void registerShortcutChangeCallback(String callingPackage, long changedSince,
-            String packageName, in List shortcutIds, in List<LocusId> locusIds,
-            in ComponentName componentName, int flags, in IShortcutChangeCallback callback);
+    void registerShortcutChangeCallback(String callingPackage, in ShortcutQueryWrapper query,
+	    in IShortcutChangeCallback callback);
     void unregisterShortcutChangeCallback(String callingPackage,
             in IShortcutChangeCallback callback);
 
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6c161fc..87dc0a1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1046,8 +1046,7 @@
             // changed callback, but that only returns shortcuts with the "key" information, so
             // that won't return disabled message.
             return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
-                    query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
-                    query.mActivity, query.mQueryFlags, user)
+                    new ShortcutQueryWrapper(query), user)
                     .getList());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1826,8 +1825,7 @@
             mShortcutChangeCallbacks.put(callback, new Pair<>(executor, proxy));
             try {
                 mService.registerShortcutChangeCallback(mContext.getPackageName(),
-                        query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
-                        query.mActivity, query.mQueryFlags, proxy);
+                        new ShortcutQueryWrapper(query), proxy);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.aidl b/core/java/android/content/pm/ShortcutQueryWrapper.aidl
new file mode 100644
index 0000000..d02600a
--- /dev/null
+++ b/core/java/android/content/pm/ShortcutQueryWrapper.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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 android.content.pm;
+
+parcelable ShortcutQueryWrapper;
+
diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.java b/core/java/android/content/pm/ShortcutQueryWrapper.java
new file mode 100644
index 0000000..c613441
--- /dev/null
+++ b/core/java/android/content/pm/ShortcutQueryWrapper.java
@@ -0,0 +1,190 @@
+/*
+ * 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 android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.LocusId;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@DataClass(genParcelable = true, genToString = true)
+public final class ShortcutQueryWrapper extends LauncherApps.ShortcutQuery implements Parcelable {
+
+    public ShortcutQueryWrapper(LauncherApps.ShortcutQuery query) {
+        this();
+        mChangedSince = query.mChangedSince;
+        mPackage = query.mPackage;
+        mLocusIds = query.mLocusIds;
+        mShortcutIds = query.mShortcutIds;
+        mActivity = query.mActivity;
+        mQueryFlags = query.mQueryFlags;
+    }
+
+    public long getChangedSince() {
+        return mChangedSince;
+    }
+
+    @Nullable
+    public String getPackage() {
+        return mPackage;
+    }
+
+    @Nullable
+    public List<LocusId> getLocusIds() {
+        return mLocusIds;
+    }
+
+    @Nullable
+    public List<String> getShortcutIds() {
+        return mShortcutIds;
+    }
+
+    @Nullable
+    public ComponentName getActivity() {
+        return mActivity;
+    }
+
+    public int getQueryFlags() {
+        return mQueryFlags;
+    }
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    public ShortcutQueryWrapper() {
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ShortcutQueryWrapper { " +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mPackage != null) flg |= 0x2;
+        if (mShortcutIds != null) flg |= 0x4;
+        if (mLocusIds != null) flg |= 0x8;
+        if (mActivity != null) flg |= 0x10;
+        dest.writeByte(flg);
+        dest.writeLong(mChangedSince);
+        if (mPackage != null) dest.writeString(mPackage);
+        if (mShortcutIds != null) dest.writeStringList(mShortcutIds);
+        if (mLocusIds != null) dest.writeParcelableList(mLocusIds, flags);
+        if (mActivity != null) dest.writeTypedObject(mActivity, flags);
+        dest.writeInt(mQueryFlags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ ShortcutQueryWrapper(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        long changedSince = in.readLong();
+        String pkg = (flg & 0x2) == 0 ? null : in.readString();
+        List<String> shortcutIds = null;
+        if ((flg & 0x4) != 0) {
+            shortcutIds = new ArrayList<>();
+            in.readStringList(shortcutIds);
+        }
+        List<LocusId> locusIds = null;
+        if ((flg & 0x8) != 0) {
+            locusIds = new ArrayList<>();
+            in.readParcelableList(locusIds, LocusId.class.getClassLoader());
+        }
+        ComponentName activity = (flg & 0x10) == 0 ? null
+                : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
+        int queryFlags = in.readInt();
+
+        this.mChangedSince = changedSince;
+        this.mPackage = pkg;
+        this.mShortcutIds = shortcutIds;
+        this.mLocusIds = locusIds;
+        this.mActivity = activity;
+        this.mQueryFlags = queryFlags;
+        com.android.internal.util.AnnotationValidations.validate(
+                QueryFlags.class, null, mQueryFlags);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ShortcutQueryWrapper> CREATOR
+            = new Parcelable.Creator<ShortcutQueryWrapper>() {
+        @Override
+        public ShortcutQueryWrapper[] newArray(int size) {
+            return new ShortcutQueryWrapper[size];
+        }
+
+        @Override
+        public ShortcutQueryWrapper createFromParcel(@NonNull Parcel in) {
+            return new ShortcutQueryWrapper(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1582049937960L,
+            codegenVersion = "1.0.14",
+            sourceFile = "frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java",
+            inputSignatures = "public  long getChangedSince()\npublic @android.annotation.Nullable java.lang.String getPackage()\npublic @android.annotation.Nullable java.util.List<android.content.LocusId> getLocusIds()\npublic @android.annotation.Nullable java.util.List<java.lang.String> getShortcutIds()\npublic @android.annotation.Nullable android.content.ComponentName getActivity()\npublic  int getQueryFlags()\nclass ShortcutQueryWrapper extends android.content.pm.LauncherApps.ShortcutQuery implements [android.os.Parcelable]\[email protected](genParcelable=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 65f45d8..ea5cc7f 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -634,17 +634,39 @@
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
             int width, int height, int densityDpi, @Nullable Surface surface, int flags,
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
-        return createVirtualDisplay(null /* projection */, name, width, height, densityDpi, surface,
-                flags, callback, handler, null /* uniqueId */);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+                height, densityDpi);
+        builder.setFlags(flags);
+        if (surface != null) {
+            builder.setSurface(surface);
+        }
+        return createVirtualDisplay(null /* projection */, builder.build(), callback, handler);
     }
 
+    // TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService)
     /** @hide */
     public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
             @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,
             int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
             @Nullable String uniqueId) {
-        return mGlobal.createVirtualDisplay(mContext, projection,
-                name, width, height, densityDpi, surface, flags, callback, handler, uniqueId);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+                height, densityDpi);
+        builder.setFlags(flags);
+        if (uniqueId != null) {
+            builder.setUniqueId(uniqueId);
+        }
+        if (surface != null) {
+            builder.setSurface(surface);
+        }
+        return createVirtualDisplay(projection, builder.build(), callback, handler);
+    }
+
+    /** @hide */
+    public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
+            @NonNull VirtualDisplayConfig virtualDisplayConfig,
+            @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+        return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback,
+                handler);
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 526db85..4d645e6 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -451,35 +451,26 @@
         }
     }
 
-    public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
-            String name, int width, int height, int densityDpi, Surface surface, int flags,
-            VirtualDisplay.Callback callback, Handler handler, String uniqueId) {
-        if (TextUtils.isEmpty(name)) {
-            throw new IllegalArgumentException("name must be non-null and non-empty");
-        }
-        if (width <= 0 || height <= 0 || densityDpi <= 0) {
-            throw new IllegalArgumentException("width, height, and densityDpi must be "
-                    + "greater than 0");
-        }
-
+    public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection,
+            @NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback,
+            Handler handler) {
         VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);
         IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
         int displayId;
         try {
-            displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken,
-                    context.getPackageName(), name, width, height, densityDpi, surface, flags,
-                    uniqueId);
+            displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper,
+                    projectionToken, context.getPackageName());
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
         if (displayId < 0) {
-            Log.e(TAG, "Could not create virtual display: " + name);
+            Log.e(TAG, "Could not create virtual display: " + virtualDisplayConfig.getName());
             return null;
         }
         Display display = getRealDisplay(displayId);
         if (display == null) {
             Log.wtf(TAG, "Could not obtain display info for newly created "
-                    + "virtual display: " + name);
+                    + "virtual display: " + virtualDisplayConfig.getName());
             try {
                 mDm.releaseVirtualDisplay(callbackWrapper);
             } catch (RemoteException ex) {
@@ -487,7 +478,8 @@
             }
             return null;
         }
-        return new VirtualDisplay(this, display, callbackWrapper, surface);
+        return new VirtualDisplay(this, display, callbackWrapper,
+                virtualDisplayConfig.getSurface());
     }
 
     public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index d22188e..c697106 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -22,6 +22,7 @@
 import android.hardware.display.Curve;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.display.WifiDisplay;
 import android.hardware.display.WifiDisplayStatus;
 import android.media.projection.IMediaProjection;
@@ -71,9 +72,9 @@
 
     // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
     // MediaProjection token for certain combinations of flags.
-    int createVirtualDisplay(in IVirtualDisplayCallback callback,
-            in IMediaProjection projectionToken, String packageName, String name,
-            int width, int height, int densityDpi, in Surface surface, int flags, String uniqueId);
+    int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig,
+            in IVirtualDisplayCallback callback, in IMediaProjection projectionToken,
+            String packageName);
 
     // No permissions required, but must be same Uid as the creator.
     void resizeVirtualDisplay(in IVirtualDisplayCallback token,
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.aidl b/core/java/android/hardware/display/VirtualDisplayConfig.aidl
new file mode 100644
index 0000000..c28f1df
--- /dev/null
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.hardware.display;
+
+parcelable VirtualDisplayConfig;
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
new file mode 100644
index 0000000..10e1c7c
--- /dev/null
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -0,0 +1,491 @@
+/*
+ * 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 android.hardware.display;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.projection.MediaProjection;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Surface;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Holds configuration used to create {@link VirtualDisplay} instances. See
+ * {@link MediaProjection#createVirtualDisplay(VirtualDisplayConfig, VirtualDisplay.Callback, Handler)}.
+ *
+ * @hide
+ */
+@DataClass(genParcelable = true, genAidl = true, genBuilder = true)
+public final class VirtualDisplayConfig implements Parcelable {
+    /**
+     * The name of the virtual display, must be non-empty.
+     */
+    @NonNull
+    private String mName;
+
+    /**
+     * The width of the virtual display in pixels. Must be greater than 0.
+     */
+    @IntRange(from = 1)
+    private int mWidth;
+
+    /**
+     * The height of the virtual display in pixels. Must be greater than 0.
+     */
+    @IntRange(from = 1)
+    private int mHeight;
+
+    /**
+     * The density of the virtual display in dpi. Must be greater than 0.
+     */
+    @IntRange(from = 1)
+    private int mDensityDpi;
+
+    /**
+     * A combination of virtual display flags.
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+     * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+     */
+    private int mFlags = 0;
+
+    /**
+     * The surface to which the content of the virtual display should be rendered, or null if
+     * there is none initially.
+     */
+    @Nullable
+    private Surface mSurface = null;
+
+    /**
+     * The unique identifier for the display. Shouldn't be displayed to the user.
+     * @hide
+     */
+    @Nullable
+    private String mUniqueId = null;
+
+    /**
+     * The id of the display that the virtual display should mirror, or
+     * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
+     */
+    private int mDisplayIdToMirror = DEFAULT_DISPLAY;
+
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ VirtualDisplayConfig(
+            @NonNull String name,
+            @IntRange(from = 1) int width,
+            @IntRange(from = 1) int height,
+            @IntRange(from = 1) int densityDpi,
+            int flags,
+            @Nullable Surface surface,
+            @Nullable String uniqueId,
+            int displayIdToMirror) {
+        this.mName = name;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mName);
+        this.mWidth = width;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mWidth,
+                "from", 1);
+        this.mHeight = height;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mHeight,
+                "from", 1);
+        this.mDensityDpi = densityDpi;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mDensityDpi,
+                "from", 1);
+        this.mFlags = flags;
+        this.mSurface = surface;
+        this.mUniqueId = uniqueId;
+        this.mDisplayIdToMirror = displayIdToMirror;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The name of the virtual display, must be non-empty.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getName() {
+        return mName;
+    }
+
+    /**
+     * The width of the virtual display in pixels. Must be greater than 0.
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 1) int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * The height of the virtual display in pixels. Must be greater than 0.
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 1) int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * The density of the virtual display in dpi. Must be greater than 0.
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 1) int getDensityDpi() {
+        return mDensityDpi;
+    }
+
+    /**
+     * A combination of virtual display flags.
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
+     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+     * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+     */
+    @DataClass.Generated.Member
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * The surface to which the content of the virtual display should be rendered, or null if
+     * there is none initially.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Surface getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * The unique identifier for the display. Shouldn't be displayed to the user.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getUniqueId() {
+        return mUniqueId;
+    }
+
+    /**
+     * The id of the display that the virtual display should mirror, or
+     * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
+     */
+    @DataClass.Generated.Member
+    public int getDisplayIdToMirror() {
+        return mDisplayIdToMirror;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        int flg = 0;
+        if (mSurface != null) flg |= 0x20;
+        if (mUniqueId != null) flg |= 0x40;
+        dest.writeInt(flg);
+        dest.writeString(mName);
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
+        dest.writeInt(mDensityDpi);
+        dest.writeInt(mFlags);
+        if (mSurface != null) dest.writeTypedObject(mSurface, flags);
+        if (mUniqueId != null) dest.writeString(mUniqueId);
+        dest.writeInt(mDisplayIdToMirror);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ VirtualDisplayConfig(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int flg = in.readInt();
+        String name = in.readString();
+        int width = in.readInt();
+        int height = in.readInt();
+        int densityDpi = in.readInt();
+        int flags = in.readInt();
+        Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR);
+        String uniqueId = (flg & 0x40) == 0 ? null : in.readString();
+        int displayIdToMirror = in.readInt();
+
+        this.mName = name;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mName);
+        this.mWidth = width;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mWidth,
+                "from", 1);
+        this.mHeight = height;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mHeight,
+                "from", 1);
+        this.mDensityDpi = densityDpi;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mDensityDpi,
+                "from", 1);
+        this.mFlags = flags;
+        this.mSurface = surface;
+        this.mUniqueId = uniqueId;
+        this.mDisplayIdToMirror = displayIdToMirror;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<VirtualDisplayConfig> CREATOR
+            = new Parcelable.Creator<VirtualDisplayConfig>() {
+        @Override
+        public VirtualDisplayConfig[] newArray(int size) {
+            return new VirtualDisplayConfig[size];
+        }
+
+        @Override
+        public VirtualDisplayConfig createFromParcel(@NonNull Parcel in) {
+            return new VirtualDisplayConfig(in);
+        }
+    };
+
+    /**
+     * A builder for {@link VirtualDisplayConfig}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private @NonNull String mName;
+        private @IntRange(from = 1) int mWidth;
+        private @IntRange(from = 1) int mHeight;
+        private @IntRange(from = 1) int mDensityDpi;
+        private int mFlags;
+        private @Nullable Surface mSurface;
+        private @Nullable String mUniqueId;
+        private int mDisplayIdToMirror;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param name
+         *   The name of the virtual display, must be non-empty.
+         * @param width
+         *   The width of the virtual display in pixels. Must be greater than 0.
+         * @param height
+         *   The height of the virtual display in pixels. Must be greater than 0.
+         * @param densityDpi
+         *   The density of the virtual display in dpi. Must be greater than 0.
+         */
+        public Builder(
+                @NonNull String name,
+                @IntRange(from = 1) int width,
+                @IntRange(from = 1) int height,
+                @IntRange(from = 1) int densityDpi) {
+            mName = name;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mName);
+            mWidth = width;
+            com.android.internal.util.AnnotationValidations.validate(
+                    IntRange.class, null, mWidth,
+                    "from", 1);
+            mHeight = height;
+            com.android.internal.util.AnnotationValidations.validate(
+                    IntRange.class, null, mHeight,
+                    "from", 1);
+            mDensityDpi = densityDpi;
+            com.android.internal.util.AnnotationValidations.validate(
+                    IntRange.class, null, mDensityDpi,
+                    "from", 1);
+        }
+
+        /**
+         * The name of the virtual display, must be non-empty.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setName(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mName = value;
+            return this;
+        }
+
+        /**
+         * The width of the virtual display in pixels. Must be greater than 0.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setWidth(@IntRange(from = 1) int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mWidth = value;
+            return this;
+        }
+
+        /**
+         * The height of the virtual display in pixels. Must be greater than 0.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setHeight(@IntRange(from = 1) int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mHeight = value;
+            return this;
+        }
+
+        /**
+         * The density of the virtual display in dpi. Must be greater than 0.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setDensityDpi(@IntRange(from = 1) int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mDensityDpi = value;
+            return this;
+        }
+
+        /**
+         * A combination of virtual display flags.
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+         * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setFlags(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10;
+            mFlags = value;
+            return this;
+        }
+
+        /**
+         * The surface to which the content of the virtual display should be rendered, or null if
+         * there is none initially.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setSurface(@NonNull Surface value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mSurface = value;
+            return this;
+        }
+
+        /**
+         * The unique identifier for the display. Shouldn't be displayed to the user.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setUniqueId(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x40;
+            mUniqueId = value;
+            return this;
+        }
+
+        /**
+         * The id of the display that the virtual display should mirror, or
+         * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setDisplayIdToMirror(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x80;
+            mDisplayIdToMirror = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull VirtualDisplayConfig build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x100; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x10) == 0) {
+                mFlags = 0;
+            }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mSurface = null;
+            }
+            if ((mBuilderFieldsSet & 0x40) == 0) {
+                mUniqueId = null;
+            }
+            if ((mBuilderFieldsSet & 0x80) == 0) {
+                mDisplayIdToMirror = DEFAULT_DISPLAY;
+            }
+            VirtualDisplayConfig o = new VirtualDisplayConfig(
+                    mName,
+                    mWidth,
+                    mHeight,
+                    mDensityDpi,
+                    mFlags,
+                    mSurface,
+                    mUniqueId,
+                    mDisplayIdToMirror);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1585179350902L,
+            codegenVersion = "1.0.15",
+            sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
+            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange(from=1L) int mWidth\nprivate @android.annotation.IntRange(from=1L) int mHeight\nprivate @android.annotation.IntRange(from=1L) int mDensityDpi\nprivate  int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\[email protected](genParcelable=true, genAidl=true, genBuilder=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 7ff954b..651494d 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -1074,6 +1074,21 @@
     }
 
     /**
+     * Returns true if this link has an IPv4 unreachable default route.
+     *
+     * @return {@code true} if there is an IPv4 unreachable default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIpv4UnreachableDefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+            if (r.isIPv4UnreachableDefault()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * For backward compatibility.
      * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
      * just yet.
@@ -1102,6 +1117,21 @@
     }
 
     /**
+     * Returns true if this link has an IPv6 unreachable default route.
+     *
+     * @return {@code true} if there is an IPv6 unreachable default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIpv6UnreachableDefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+            if (r.isIPv6UnreachableDefault()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * For backward compatibility.
      * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
      * just yet.
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index dbdaa4c..e550f85 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -426,6 +426,16 @@
     }
 
     /**
+     * Indicates if this route is an unreachable default route.
+     *
+     * @return {@code true} if it's an unreachable route with prefix length of 0.
+     * @hide
+     */
+    private boolean isUnreachableDefaultRoute() {
+        return mType == RTN_UNREACHABLE && mDestination.getPrefixLength() == 0;
+    }
+
+    /**
      * Indicates if this route is an IPv4 default route.
      * @hide
      */
@@ -434,6 +444,14 @@
     }
 
     /**
+     * Indicates if this route is an IPv4 unreachable default route.
+     * @hide
+     */
+    public boolean isIPv4UnreachableDefault() {
+        return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet4Address;
+    }
+
+    /**
      * Indicates if this route is an IPv6 default route.
      * @hide
      */
@@ -442,6 +460,14 @@
     }
 
     /**
+     * Indicates if this route is an IPv6 unreachable default route.
+     * @hide
+     */
+    public boolean isIPv6UnreachableDefault() {
+        return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet6Address;
+    }
+
+    /**
      * Indicates if this route is a host route (ie, matches only a single host address).
      *
      * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6,
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 20e5f24..683993f7 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -251,6 +251,11 @@
                     }
                 }
             }
+            // For gathering this debug output, we're making synchronous binder calls
+            // out of system_server to all processes hosting binder objects it holds a reference to;
+            // since some of those processes might be frozen, we don't want to block here
+            // forever. Disable the freezer.
+            Process.enableFreezer(false);
             for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
                 BinderProxy bp = weakRef.get();
                 String key;
@@ -273,6 +278,7 @@
                     counts.put(key, i + 1);
                 }
             }
+            Process.enableFreezer(true);
             Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
                     new Map.Entry[counts.size()]);
 
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index fb81d67..e86aa62 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -266,6 +266,13 @@
     public static final String NAMESPACE_SCHEDULER = "scheduler";
 
     /**
+     * Namespace for settings statistics features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
+
+    /**
      * Namespace for storage-related features.
      *
      * @deprecated Replace storage namespace with storage_native_boot.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 35f955f..3534bb0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1747,17 +1747,18 @@
                 || !mBlastSurfaceControl.isValid()) {
             return null;
         }
+
+        Surface ret = null;
         if (mBlastBufferQueue == null) {
             mBlastBufferQueue = new BLASTBufferQueue(
                 mBlastSurfaceControl, width, height);
+            // We only return the Surface the first time, as otherwise
+            // it hasn't changed and there is no need to update.
+            ret = mBlastBufferQueue.getSurface();
         }
         mBlastBufferQueue.update(mBlastSurfaceControl, width, height);
 
-        mTransaction.show(mBlastSurfaceControl)
-            .reparent(mBlastSurfaceControl, mSurfaceControl)
-            .apply();
-
-        return mBlastBufferQueue.getSurface();
+        return ret;
     }
 
     private void setBoundsLayerCrop() {
@@ -7352,8 +7353,14 @@
             if (!mUseBLASTAdapter) {
                 mSurface.copyFrom(mSurfaceControl);
             } else {
-                mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x,
-                        mSurfaceSize.y));
+                final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x,
+                    mSurfaceSize.y);
+                // If blastSurface == null that means it hasn't changed since the last time we
+                // called. In this situation, avoid calling transferFrom as we would then
+                // inc the generation ID and cause EGL resources to be recreated.
+                if (blastSurface != null) {
+                    mSurface.transferFrom(blastSurface);
+                }
             }
         } else {
             destroySurface();
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 561ee60..316a5f2 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -36,7 +36,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 
 import java.util.List;
@@ -70,8 +69,7 @@
 public final class WindowManagerImpl implements WindowManager {
     @UnsupportedAppUsage
     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
-    @VisibleForTesting
-    public final Context mContext;
+    private final Context mContext;
     private final Window mParentWindow;
 
     private IBinder mDefaultToken;
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index f007768..99b4b5f 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -41,17 +41,17 @@
 public class DecorContext extends ContextThemeWrapper {
     private PhoneWindow mPhoneWindow;
     private WindowManager mWindowManager;
-    private Resources mResources;
+    private Resources mActivityResources;
     private ContentCaptureManager mContentCaptureManager;
 
-    private WeakReference<Context> mContext;
+    private WeakReference<Context> mActivityContext;
 
     // TODO(b/149928768): Non-activity context can be passed.
     @VisibleForTesting
-    public DecorContext(Context baseContext, Context context) {
-        super(baseContext.createDisplayContext(context.getDisplayNoVerify()), null);
-        mContext = new WeakReference<>(context);
-        mResources = context.getResources();
+    public DecorContext(Context context, Context activityContext) {
+        super(context.createDisplayContext(activityContext.getDisplayNoVerify()), null);
+        mActivityContext = new WeakReference<>(activityContext);
+        mActivityResources = activityContext.getResources();
     }
 
     void setPhoneWindow(PhoneWindow phoneWindow) {
@@ -61,56 +61,58 @@
 
     @Override
     public Object getSystemService(String name) {
-        final Context context = mContext.get();
         if (Context.WINDOW_SERVICE.equals(name)) {
-            if (context != null && mWindowManager == null) {
-                WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(name);
+            if (mWindowManager == null) {
+                WindowManagerImpl wm =
+                        (WindowManagerImpl) super.getSystemService(Context.WINDOW_SERVICE);
                 mWindowManager = wm.createLocalWindowManager(mPhoneWindow);
             }
             return mWindowManager;
         }
         if (Context.CONTENT_CAPTURE_MANAGER_SERVICE.equals(name)) {
-            if (context != null && mContentCaptureManager == null) {
-                mContentCaptureManager = (ContentCaptureManager) context.getSystemService(name);
+            if (mContentCaptureManager == null) {
+                Context activityContext = mActivityContext.get();
+                if (activityContext != null) {
+                    mContentCaptureManager = (ContentCaptureManager) activityContext
+                            .getSystemService(name);
+                }
             }
             return mContentCaptureManager;
         }
-        // LayoutInflater and WallpaperManagerService should also be obtained from context
-        // instead of application context.
-        return (context != null) ? context.getSystemService(name) : super.getSystemService(name);
+        return super.getSystemService(name);
     }
 
     @Override
     public Resources getResources() {
-        Context context = mContext.get();
+        Context activityContext = mActivityContext.get();
         // Attempt to update the local cached Resources from the activity context. If the activity
         // is no longer around, return the old cached values.
-        if (context != null) {
-            mResources = context.getResources();
+        if (activityContext != null) {
+            mActivityResources = activityContext.getResources();
         }
 
-        return mResources;
+        return mActivityResources;
     }
 
     @Override
     public AssetManager getAssets() {
-        return mResources.getAssets();
+        return mActivityResources.getAssets();
     }
 
     @Override
     public AutofillOptions getAutofillOptions() {
-        Context context = mContext.get();
-        if (context != null) {
-            return context.getAutofillOptions();
+        Context activityContext = mActivityContext.get();
+        if (activityContext != null) {
+            return activityContext.getAutofillOptions();
         }
         return null;
     }
 
     @Override
     public ContentCaptureOptions getContentCaptureOptions() {
-        Context context = mContext.get();
-        if (context != null) {
-            return context.getContentCaptureOptions();
+        Context activityContext = mActivityContext.get();
+        if (activityContext != null) {
+            return activityContext.getContentCaptureOptions();
         }
         return null;
     }
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 0c74387..7c32ca6 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -696,7 +696,7 @@
         return -1;
     }
 
-    return si.totalram;
+    return static_cast<jlong>(si.totalram) * si.mem_unit;
 }
 
 /*
diff --git a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
new file mode 100644
index 0000000..8f8488f
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.content.pm;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.LocusId;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ShortcutQueryWrapperTest {
+
+    private static final long CHANGED_SINCE = TimeUnit.SECONDS.toMillis(1);
+    private static final String PACKAGE_NAME = "com.android.test";
+    private static final List<String> SHORTCUT_IDS = Lists.newArrayList("s1", "s2", "s3");
+    private static final List<LocusId> LOCUS_IDS = Lists.newArrayList(
+            new LocusId("id1"), new LocusId("id2"), new LocusId("id3"));
+    private static final ComponentName COMPONENT_NAME = new ComponentName(
+            PACKAGE_NAME, "ShortcutQueryTest");
+    private static final int QUERY_FLAG = LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS;
+
+    private ShortcutQueryWrapper mShortcutQuery;
+
+    @Before
+    public void setUp() throws Exception {
+        mShortcutQuery = new ShortcutQueryWrapper(new LauncherApps.ShortcutQuery()
+                .setChangedSince(CHANGED_SINCE)
+                .setPackage(PACKAGE_NAME)
+                .setShortcutIds(SHORTCUT_IDS)
+                .setLocusIds(LOCUS_IDS)
+                .setActivity(COMPONENT_NAME)
+                .setQueryFlags(QUERY_FLAG));
+    }
+
+    @Test
+    public void testWriteAndReadFromParcel() {
+        Parcel p = Parcel.obtain();
+        mShortcutQuery.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        ShortcutQueryWrapper q = ShortcutQueryWrapper.CREATOR.createFromParcel(p);
+        assertEquals("Changed since doesn't match!", CHANGED_SINCE, q.getChangedSince());
+        assertEquals("Package name doesn't match!", PACKAGE_NAME, q.getPackage());
+        assertEquals("Shortcut ids doesn't match", SHORTCUT_IDS, q.getShortcutIds());
+        assertEquals("Locus ids doesn't match", LOCUS_IDS, q.getLocusIds());
+        assertEquals("Component name doesn't match", COMPONENT_NAME, q.getActivity());
+        assertEquals("Query flag doesn't match", QUERY_FLAG, q.getQueryFlags());
+        p.recycle();
+    }
+}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index d649b94..61f58b0 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -645,14 +645,60 @@
 
         Properties modifiedProperties2 = new Properties.Builder(namespaceToBan2).setString(KEY,
                 VALUE)
+                .setString(KEY3, NULL_VALUE).setString(KEY4, VALUE2).build();
+        DeviceConfig.setProperties(modifiedProperties2);
+        modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan2);
+        assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY3, KEY4);
+        assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+        assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(VALUE2);
+        // Since value is null DEFAULT_VALUE should be returned
+        assertThat(modifiedProperties2.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+    }
+
+    @Test
+    public void allConfigsUnbannedIfAnyUnbannedConfigUpdated()
+            throws DeviceConfig.BadConfigException {
+        // Given namespaces will be permanently banned, thus they need to be different every time
+        final String namespaceToBan1 = NAMESPACE + System.currentTimeMillis();
+        final String namespaceToBan2 = NAMESPACE + System.currentTimeMillis() + 1;
+
+        // Set namespaces properties
+        Properties properties1 = new Properties.Builder(namespaceToBan1).setString(KEY, VALUE)
+                .setString(KEY4, NULL_VALUE).build();
+        DeviceConfig.setProperties(properties1);
+        Properties properties2 = new Properties.Builder(namespaceToBan2).setString(KEY2, VALUE2)
+                .setString(KEY4, NULL_VALUE).build();
+        DeviceConfig.setProperties(properties2);
+
+        // Ban namespace with related properties
+        DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan1);
+        DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan2);
+
+        // Verify given namespace properties are banned
+        assertThrows(DeviceConfig.BadConfigException.class,
+                () -> DeviceConfig.setProperties(properties1));
+        assertThrows(DeviceConfig.BadConfigException.class,
+                () -> DeviceConfig.setProperties(properties2));
+
+        // Modify properties and verify we can set them
+        Properties modifiedProperties1 = new Properties.Builder(namespaceToBan1).setString(KEY,
+                VALUE)
                 .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build();
         DeviceConfig.setProperties(modifiedProperties1);
-        modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan1);
-        assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY2, KEY4);
-        assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
-        assertThat(modifiedProperties2.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+        modifiedProperties1 = DeviceConfig.getProperties(namespaceToBan1);
+        assertThat(modifiedProperties1.getKeyset()).containsExactly(KEY, KEY2, KEY4);
+        assertThat(modifiedProperties1.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+        assertThat(modifiedProperties1.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
         // Since value is null DEFAULT_VALUE should be returned
-        assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+        assertThat(modifiedProperties1.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+
+        // verify that other banned namespaces are unbanned now.
+        DeviceConfig.setProperties(properties2);
+        Properties result = DeviceConfig.getProperties(namespaceToBan2);
+        assertThat(result.getKeyset()).containsExactly(KEY2, KEY4);
+        assertThat(result.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+        // Since value is null DEFAULT_VALUE should be returned
+        assertThat(result.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
     }
 
     // TODO(mpape): resolve b/142727848 and re-enable listener tests
diff --git a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
index d019704..3e40466 100644
--- a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
@@ -20,24 +20,19 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.app.Activity;
-import android.app.EmptyActivity;
 import android.content.Context;
 import android.hardware.display.DisplayManagerGlobal;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
-import android.view.WindowManager;
-import android.view.WindowManagerImpl;
 
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -51,13 +46,9 @@
     private Context mContext;
     private static final int EXTERNAL_DISPLAY = DEFAULT_DISPLAY + 1;
 
-    @Rule
-    public ActivityTestRule<EmptyActivity> mActivityRule =
-            new ActivityTestRule<>(EmptyActivity.class);
-
     @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
     }
 
     @Test
@@ -85,19 +76,4 @@
         Display associatedDisplay = decorContext.getDisplay();
         assertEquals(expectedDisplayId, associatedDisplay.getDisplayId());
     }
-
-    @Test
-    public void testGetWindowManagerFromVisualDecorContext() throws Throwable {
-        mActivityRule.runOnUiThread(() -> {
-            Activity activity = mActivityRule.getActivity();
-            final DecorContext decorContext = new DecorContext(mContext.getApplicationContext(),
-                    activity);
-            WindowManagerImpl actualWm = (WindowManagerImpl)
-                    decorContext.getSystemService(WindowManager.class);
-            WindowManagerImpl expectedWm = (WindowManagerImpl)
-                    activity.getSystemService(WindowManager.class);
-            // Verify that window manager is from activity not application context.
-            assertEquals(expectedWm.mContext, actualWm.mContext);
-        });
-    }
 }
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index ad9486c..405410a 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.hardware.cas.V1_0.HidlCasPluginDescriptor;
 import android.hardware.cas.V1_0.ICas;
@@ -1076,6 +1077,17 @@
         }
     }
 
+    /**
+     * Release Cas session. This is primarily used as a test API for CTS.
+     * @hide
+     */
+    @TestApi
+    public void forceResourceLost() {
+        if (mResourceListener != null) {
+            mResourceListener.onReclaimResources();
+        }
+    }
+
     @Override
     public void close() {
         if (mICas != null) {
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 632cfb0..37e1415 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
+import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
 import android.os.Handler;
@@ -100,11 +101,18 @@
             int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
         DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-        int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
-        return dm.createVirtualDisplay(this, name, width, height, dpi, surface,
-                    flags | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR |
-                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callback, handler,
-                    null /* uniqueId */);
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+                | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+        if (isSecure) {
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+        }
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+                height, dpi);
+        builder.setFlags(flags);
+        if (surface != null) {
+            builder.setSurface(surface);
+        }
+        return dm.createVirtualDisplay(this, builder.build(), callback, handler);
     }
 
     /**
@@ -133,9 +141,35 @@
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
             int width, int height, int dpi, int flags, @Nullable Surface surface,
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
-        DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-        return dm.createVirtualDisplay(this, name, width, height, dpi, surface, flags, callback,
-                handler, null /* uniqueId */);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+                height, dpi);
+        builder.setFlags(flags);
+        if (surface != null) {
+            builder.setSurface(surface);
+        }
+        return createVirtualDisplay(builder.build(), callback, handler);
+    }
+
+    /**
+     * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
+     * contents of the screen.
+     *
+     * @param virtualDisplayConfig The arguments for the virtual display configuration. See
+     * {@link VirtualDisplayConfig} for using it.
+     * @param callback Callback to call when the virtual display's state
+     * changes, or null if none.
+     * @param handler The {@link android.os.Handler} on which the callback should be
+     * invoked, or null if the callback should be invoked on the calling
+     * thread's main {@link android.os.Looper}.
+     *
+     * @see android.hardware.display.VirtualDisplay
+     * @hide
+     */
+    @Nullable
+    public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+            @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+        DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+        return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler);
     }
 
     /**
diff --git a/media/packages/BluetoothMidiService/Android.bp b/media/packages/BluetoothMidiService/Android.bp
index f45114a..77e6a14 100644
--- a/media/packages/BluetoothMidiService/Android.bp
+++ b/media/packages/BluetoothMidiService/Android.bp
@@ -1,6 +1,35 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_library {
+    name: "BluetoothMidiLib",
+    srcs: [
+        "src/**/*.java",
+    ],
+    platform_apis: true,
+    plugins: ["java_api_finder"],
+    manifest: "AndroidManifestBase.xml",
+}
+
 android_app {
     name: "BluetoothMidiService",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+    ],
     platform_apis: true,
     certificate: "platform",
+    manifest: "AndroidManifest.xml",
 }
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
index 1cfd55d..4042ce8b 100644
--- a/media/packages/BluetoothMidiService/AndroidManifest.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -1,12 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2015 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.
+ */
+-->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
         package="com.android.bluetoothmidiservice"
+        android:versionCode="1"
+        android:versionName="R-initial"
         >
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
     <uses-feature android:name="android.software.midi" android:required="true"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
 
     <application
+        tools:replace="android:label"
         android:label="@string/app_name">
         <service android:name=".BluetoothMidiService"
             android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
diff --git a/media/packages/BluetoothMidiService/AndroidManifestBase.xml b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
new file mode 100644
index 0000000..ebe62b0
--- /dev/null
+++ b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.bluetoothmidiservice"
+          android:versionCode="1"
+          android:versionName="R-initial"
+          >
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+    <application
+        android:label="BluetoothMidi"
+        android:defaultToDeviceProtectedStorage="true"
+        android:directBootAware="true">
+    </application>
+</manifest>
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
index c51c8fa..8d18b77 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
@@ -70,7 +70,9 @@
         }
 
         byte header = buffer[0];
-        if ((header & 0xC0) != 0x80) {
+        // Check for the header bit 7.
+        // Ignore the reserved bit 6.
+        if ((header & 0x80) != 0x80) {
             Log.e(TAG, "packet does not start with header");
             return;
         }
diff --git a/media/packages/BluetoothMidiService/tests/unit/Android.bp b/media/packages/BluetoothMidiService/tests/unit/Android.bp
new file mode 100644
index 0000000..4d4ae9e
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+    name: "BluetoothMidiTests",
+    srcs: ["src/**/*.java"],
+    certificate: "platform",
+    static_libs: [
+        //"frameworks-base-testutils",
+        "android-support-test",
+        "androidx.test.core",
+        "androidx.test.ext.truth",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "platform-test-annotations",
+        "BluetoothMidiLib",
+    ],
+    test_suites: ["device-tests"],
+    libs: [
+        "framework-res",
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+}
diff --git a/media/packages/BluetoothMidiService/tests/unit/AndroidManifest.xml b/media/packages/BluetoothMidiService/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..4d27e1e
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.bluetoothmidiservice.tests.unit">
+    <uses-sdk
+        android:minSdkVersion="29"
+        android:targetSdkVersion="29" />
+
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.bluetoothmidiservice.tests.unit"
+        android:label="Bluetooth MIDI Service tests">
+    </instrumentation>
+</manifest>
diff --git a/media/packages/BluetoothMidiService/tests/unit/AndroidTest.xml b/media/packages/BluetoothMidiService/tests/unit/AndroidTest.xml
new file mode 100644
index 0000000..02e7f0d
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<configuration description="Runs Bluetooth MIDI Service Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="BluetoothMidiTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="BLEMidiTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.bluetoothmidiservice.tests.unit" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/AccumulatingMidiReceiver.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/AccumulatingMidiReceiver.java
new file mode 100644
index 0000000..57859bc
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/AccumulatingMidiReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+import android.util.Log;
+
+import com.android.internal.midi.MidiFramer;
+
+import java.util.ArrayList;
+
+class AccumulatingMidiReceiver extends MidiReceiver {
+    private static final String TAG = "AccumulatingMidiReceiver";
+    ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
+    ArrayList<Long> mTimestamps = new ArrayList<Long>();
+
+    public void onSend(byte[] buffer, int offset, int count, long timestamp) {
+        Log.d(TAG, "onSend() passed " + MidiFramer.formatMidiData(buffer, offset, count));
+        byte[] actualRow = new byte[count];
+        System.arraycopy(buffer, offset, actualRow, 0, count);
+        mBuffers.add(actualRow);
+        mTimestamps.add(timestamp);
+    }
+
+    byte[][] getBuffers() {
+        return mBuffers.toArray(new byte[mBuffers.size()][]);
+    }
+
+    Long[] getTimestamps() {
+        return mTimestamps.toArray(new Long[mTimestamps.size()]);
+    }
+}
+
diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java
new file mode 100644
index 0000000..3285f59
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.midi.MidiFramer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * End to end testing of the Bluetooth Encoder and Decoder
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothMidiCodecTest {
+
+    private static final String TAG = "BluetoothMidiCodecTest";
+    private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+    private static final long NANOS_PER_MSEC = 1000000L;
+
+    static class EncoderDecoderChecker implements PacketEncoder.PacketReceiver {
+        BluetoothPacketEncoder mEncoder;
+        BluetoothPacketDecoder mDecoder;
+        AccumulatingMidiReceiver mReceiver;
+        MidiFramer mFramer;
+        AccumulatingMidiReceiver mBypassReceiver;
+        MidiFramer mBypassFramer;
+        int mMaxPacketsPerConnection;
+        int mConnectionIntervalMillis;
+        BlockingQueue<byte[]> mPacketQueue;
+        ScheduledExecutorService mScheduler;
+
+        EncoderDecoderChecker() {
+            this(2, 15, 20);
+        }
+
+        EncoderDecoderChecker(
+                int maxPacketsPerConnection,
+                int connectionIntervalMillis,
+                int maxBytesPerPacket) {
+            mMaxPacketsPerConnection = maxPacketsPerConnection;
+            mConnectionIntervalMillis = connectionIntervalMillis;
+            mEncoder = new BluetoothPacketEncoder(this, maxBytesPerPacket);
+            mDecoder = new BluetoothPacketDecoder(maxBytesPerPacket);
+            mReceiver = new AccumulatingMidiReceiver();
+            mFramer = new MidiFramer(mReceiver);
+            mBypassReceiver = new AccumulatingMidiReceiver();
+            mBypassFramer = new MidiFramer(mBypassReceiver);
+            mScheduler = Executors.newSingleThreadScheduledExecutor();
+            mPacketQueue = new LinkedBlockingDeque<>(maxPacketsPerConnection);
+        }
+
+        void processQueue() throws InterruptedException {
+            for (int i = 0; i < mMaxPacketsPerConnection; i++) {
+                byte[] packet = mPacketQueue.poll(0, TimeUnit.SECONDS);
+                if (packet == null) break;
+                Log.d(TAG, "decode " + MidiFramer.formatMidiData(packet, 0, packet.length));
+                mDecoder.decodePacket(packet, mFramer);
+            }
+            Log.d(TAG, "call writeComplete()");
+            mEncoder.writeComplete();
+        }
+
+        public void start() {
+            mScheduler.scheduleAtFixedRate(
+                    () -> {
+                        Log.d(TAG, "run scheduled task");
+                        try {
+                            processQueue();
+                        } catch (Exception e) {
+                            assertEquals(null, e);
+                        }
+                    },
+                    mConnectionIntervalMillis, // initial delay
+                    mConnectionIntervalMillis, // period
+                    TimeUnit.MILLISECONDS);
+        }
+
+        public void stop() {
+            // TODO wait for queue to empty
+            mScheduler.shutdown();
+        }
+
+        // TODO Should this block?
+        // Store the packets and then write them from a periodic task.
+        @Override
+        public void writePacket(byte[] buffer, int count) {
+            Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count));
+            byte[] packet = new byte[count];
+            System.arraycopy(buffer, 0, packet, 0, count);
+            try {
+                mPacketQueue.put(packet);
+            } catch (Exception e) {
+                assertEquals(null, e);
+            }
+            Log.d(TAG, "writePacket() returns");
+        }
+
+        void test(final byte[][] midi)
+                throws IOException, InterruptedException {
+            test(midi, 2);
+        }
+
+        // Send the MIDI messages through the encoder,
+        // then through the decoder,
+        // then gather the resulting MIDI and compare the results.
+        void test(final byte[][] midi, int intervalMillis)
+                throws IOException, InterruptedException {
+            start();
+            long timestamp = 0;
+            // Send all of the MIDI messages and gather the response.
+            for (int i = 0; i < midi.length; i++) {
+                byte[] outMessage = midi[i];
+                Log.d(TAG, "outMessage "
+                        + MidiFramer.formatMidiData(outMessage, 0, outMessage.length));
+                mEncoder.send(outMessage, 0, outMessage.length, timestamp);
+                timestamp += 2 * NANOS_PER_MSEC;
+                // Also send a copy through a MidiFramer to align the messages.
+                mBypassFramer.send(outMessage, 0, outMessage.length, timestamp);
+            }
+            Thread.sleep(200);
+            stop();
+
+            // Compare the gathered rows with the expected rows.
+            byte[][] expectedMessages = mBypassReceiver.getBuffers();
+            byte[][] inMessages = mReceiver.getBuffers();
+            Log.d(TAG, "expectedMessage length = " + expectedMessages.length
+                    + ", inMessages length = " + inMessages.length);
+            assertEquals(expectedMessages.length, inMessages.length);
+            Long[] actualTimestamps = mReceiver.getTimestamps();
+            long previousTime = 0;
+            for (int i = 0; i < expectedMessages.length; i++) {
+                byte[] expectedMessage = expectedMessages[i];
+                Log.d(TAG, "expectedMessage = "
+                        + MidiFramer.formatMidiData(expectedMessage,
+                        0, expectedMessage.length));
+                byte[] actualMessage = inMessages[i];
+                Log.d(TAG, "actualMessage   = "
+                        + MidiFramer.formatMidiData(actualMessage, 0, actualMessage.length));
+                assertArrayEquals(expectedMessage, actualMessage);
+                // Are the timestamps monotonic?
+                long currentTime =  actualTimestamps[i];
+                Log.d(TAG, "previousTime   = " + previousTime
+                        + ", currentTime   = " + currentTime);
+                assertTrue(currentTime >= previousTime);
+                previousTime = currentTime;
+            }
+        }
+    }
+
+    @Test
+    public void testOneNoteOn() throws IOException, InterruptedException {
+        final byte[][] midi = {
+                {(byte) 0x90, 0x40, 0x64}
+                };
+        EncoderDecoderChecker checker = new EncoderDecoderChecker();
+        checker.test(midi);
+    }
+
+    @Test
+    public void testTwoNoteOnSameTime() throws IOException, InterruptedException {
+        final byte[][] midi = {
+                {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x70}
+                };
+        EncoderDecoderChecker checker = new EncoderDecoderChecker();
+        checker.test(midi);
+    }
+
+    @Test
+    public void testTwoNoteOnStaggered() throws IOException, InterruptedException {
+        final byte[][] midi = {
+                {(byte) 0x90, 0x40, 0x64},
+                {(byte) 0x90, 0x47, 0x70}
+                };
+        EncoderDecoderChecker checker = new EncoderDecoderChecker();
+        checker.test(midi);
+    }
+
+    public void checkNoteBurst(int maxPacketsPerConnection,
+            int period,
+            int maxBytesPerPacket) throws IOException, InterruptedException {
+        final int numNotes = 100;
+        final byte[][] midi = new byte[numNotes][];
+        int channel = 2;
+        for (int i = 0; i < numNotes; i++) {
+            byte[] message = {(byte) (0x90 + channel), (byte) (i + 1), 0x64};
+            midi[i] = message;
+            channel ^= 1;
+        }
+        EncoderDecoderChecker checker = new EncoderDecoderChecker(
+                maxPacketsPerConnection, 15, maxBytesPerPacket);
+        checker.test(midi, period);
+    }
+
+    @Test
+    public void testNoteBurstM1P6() throws IOException, InterruptedException {
+        checkNoteBurst(1, 6, 20);
+    }
+    @Test
+    public void testNoteBurstM1P2() throws IOException, InterruptedException {
+        checkNoteBurst(1, 2, 20);
+    }
+    @Test
+    public void testNoteBurstM2P6() throws IOException, InterruptedException {
+        checkNoteBurst(2, 6, 20);
+    }
+    @Test
+    public void testNoteBurstM2P2() throws IOException, InterruptedException {
+        checkNoteBurst(2, 2, 20);
+    }
+    @Test
+    public void testNoteBurstM2P0() throws IOException, InterruptedException {
+        checkNoteBurst(2, 0, 20);
+    }
+    @Test
+    public void testNoteBurstM2P6B21() throws IOException, InterruptedException {
+        checkNoteBurst(2, 6, 21);
+    }
+    @Test
+    public void testNoteBurstM2P2B21() throws IOException, InterruptedException {
+        checkNoteBurst(2, 2, 21);
+    }
+    @Test
+    public void testNoteBurstM2P0B21() throws IOException, InterruptedException {
+        checkNoteBurst(2, 0, 21);
+    }
+}
diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiDecoderTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiDecoderTest.java
new file mode 100644
index 0000000..6ecc539
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiDecoderTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.midi.MidiFramer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothMidiDecoderTest {
+
+    private static final String TAG = "BluetoothMidiDecoderTest";
+    private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+    private static final long NANOS_PER_MSEC = 1000000L;
+
+    static class DecoderChecker {
+        AccumulatingMidiReceiver mReceiver;
+        BluetoothPacketDecoder mDecoder;
+
+        DecoderChecker() {
+            mReceiver = new AccumulatingMidiReceiver();
+            final int maxBytes = 20;
+            mDecoder = new BluetoothPacketDecoder(maxBytes);
+        }
+
+        void compareWithExpected(final byte[][] expectedMessages) {
+            byte[][] actualRows = mReceiver.getBuffers();
+            Long[] actualTimestamps = mReceiver.getTimestamps();
+            long previousTime = 0;
+            // Compare the gathered with the expected.
+            assertEquals(expectedMessages.length, actualRows.length);
+            for (int i = 0; i < expectedMessages.length; i++) {
+                byte[] expectedRow = expectedMessages[i];
+                Log.d(TAG, "expectedRow = "
+                        + MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length));
+                byte[] actualRow = actualRows[i];
+                Log.d(TAG, "actualRow   = "
+                        + MidiFramer.formatMidiData(actualRow, 0, actualRow.length));
+                assertArrayEquals(expectedRow, actualRow);
+                // Are the timestamps monotonic?
+                long currentTime =  actualTimestamps[i];
+                Log.d(TAG, "previousTime   = " + previousTime + ", currentTime   = " + currentTime);
+                assertTrue(currentTime >= previousTime);
+                previousTime = currentTime;
+            }
+        }
+
+        void decodePacket(byte[] packet) throws IOException {
+            mDecoder.decodePacket(packet, mReceiver);
+        }
+
+        void decodePackets(byte[][] multiplePackets) throws IOException {
+            try {
+                for (int i = 0; i < multiplePackets.length; i++) {
+                    byte[] packet = multiplePackets[i];
+                    mDecoder.decodePacket(packet, mReceiver);
+                }
+            } catch (Exception e) {
+                assertEquals(null, e);
+            }
+        }
+
+        void test(byte[] encoded, byte[][] decoded) throws IOException {
+            decodePacket(encoded);
+            compareWithExpected(decoded);
+        }
+
+        void test(byte[][] encoded, byte[][] decoded) throws IOException {
+            decodePackets(encoded);
+            compareWithExpected(decoded);
+        }
+    }
+
+    @Test
+    public void testOneNoteOn() throws IOException {
+        final byte[] encoded = {
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x90, 0x40, 0x64
+                };
+        final byte[][] decoded = {
+                {(byte) 0x90, 0x40, 0x64}
+                };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+    @Test
+    public void testReservedHeaderBit() throws IOException {
+        final byte[] encoded = {
+                // Decoder should ignore the reserved bit.
+                (byte) (0x80 | 0x40), // set RESERVED bit in header!
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x90, 0x40, 0x64
+                };
+        final byte[][] decoded = {
+                {(byte) 0x90, 0x40, 0x64}
+                };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+    @Test
+    public void testTwoNotesOnRunning() throws IOException {
+        final byte[] encoded = {
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x90, 0x40, 0x64,
+                (byte) 0x85, // timestamp
+                (byte) 0x42, 0x70
+                };
+        final byte[][] decoded = {
+                {(byte) 0x90, 0x40, 0x64},
+                {(byte) 0x42, 0x70}
+                };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+    @Test
+    public void testTwoNoteOnsTwoChannels() throws IOException {
+        final byte[] encoded = {
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x93, 0x40, 0x60,
+                // two channels so no running status
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x95, 0x47, 0x64
+                };
+        final byte[][] decoded = {
+                {(byte) 0x93, 0x40, 0x60,  (byte) 0x95, 0x47, 0x64}
+                };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+    @Test
+    public void testTwoNoteOnsOverTime() throws IOException {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x98, 0x45, 0x60
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x82, // timestamp advanced by 2 msec
+                (byte) 0x90, 0x40, 0x64,
+                (byte) 0x84, // timestamp needed because of time delay
+                // encoder uses running status
+                0x47, 0x72
+                }};
+        final byte[][] decoded = {
+                {(byte) 0x98, 0x45, 0x60},
+                {(byte) 0x90, 0x40, 0x64},
+                {(byte) 0x47, 0x72}
+                };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+    @Test
+    public void testSysExBasic() throws IOException {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // timestamp
+                (byte) 0xF0, 0x7D, // Begin prototyping SysEx
+                0x01, 0x02, 0x03, 0x04, 0x05,
+                (byte) 0x80, // timestamp
+                (byte) 0xF7 // End SysEx
+                }};
+        final byte[][] decoded = {
+                {(byte) 0xF0, 0x7D, // experimental SysEx
+                0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xF7}
+                };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+    @Test
+    public void testSysExTwoPackets() throws IOException {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // timestamp
+                (byte) 0xF0, 0x7D, // Begin prototyping SysEx
+                0x01, 0x02
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                0x03, 0x04, 0x05,
+                (byte) 0x80, // timestamp
+                (byte) 0xF7 // End SysEx
+                }};
+        final byte[][] decoded = {
+            {(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx
+            {0x03, 0x04, 0x05, (byte) 0xF7}
+        };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+    @Test
+    public void testSysExThreePackets() throws IOException {
+        final byte[][] encoded = {
+                {(byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // timestamp
+                (byte) 0xF0, 0x7D, // Begin prototyping SysEx
+                0x01, 0x02
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                0x03, 0x04, 0x05,
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                0x06, 0x07, 0x08,
+                (byte) 0x80, // timestamp
+                (byte) 0xF7 // End SysEx
+                }};
+        final byte[][] decoded = {
+                {(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx
+                {0x03, 0x04, 0x05},
+                {0x06, 0x07, 0x08, (byte) 0xF7}
+                };
+        new DecoderChecker().test(encoded, decoded);
+    }
+
+}
diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java
new file mode 100644
index 0000000..a169c0d
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.midi.MidiFramer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothMidiEncoderTest {
+
+    private static final String TAG = "BluetoothMidiEncoderTest";
+    private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+    private static final long NANOS_PER_MSEC = 1000000L;
+
+    static class AccumulatingPacketReceiver implements PacketEncoder.PacketReceiver {
+        ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
+
+        public void writePacket(byte[] buffer, int count) {
+            byte[] actualRow = new byte[count];
+            Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count));
+            System.arraycopy(buffer, 0, actualRow, 0, count);
+            mBuffers.add(actualRow);
+        }
+
+        byte[][] getBuffers() {
+            return mBuffers.toArray(new byte[mBuffers.size()][]);
+        }
+    }
+
+    static class EncoderChecker {
+        AccumulatingPacketReceiver mReceiver;
+        BluetoothPacketEncoder mEncoder;
+
+        EncoderChecker() {
+            mReceiver = new AccumulatingPacketReceiver();
+            final int maxBytes = 20;
+            mEncoder = new BluetoothPacketEncoder(mReceiver, maxBytes);
+        }
+
+        void send(byte[] data) throws IOException {
+            send(data, 0);
+        }
+
+        void send(byte[] data, long timestamp) throws IOException {
+            Log.d(TAG, "send " + MidiFramer.formatMidiData(data, 0, data.length));
+            mEncoder.send(data, 0, data.length, timestamp);
+        }
+
+        void compareWithExpected(final byte[][] expected) {
+            byte[][] actualRows = mReceiver.getBuffers();
+            assertEquals(expected.length, actualRows.length);
+            // Compare the gathered rows with the expected rows.
+            for (int i = 0; i < expected.length; i++) {
+                byte[] expectedRow = expected[i];
+                Log.d(TAG, "expectedRow = "
+                        + MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length));
+                byte[] actualRow = actualRows[i];
+                Log.d(TAG, "actualRow   = "
+                        + MidiFramer.formatMidiData(actualRow, 0, actualRow.length));
+                assertEquals(expectedRow.length, actualRow.length);
+                for (int k = 0; k < expectedRow.length; k++) {
+                    assertEquals(expectedRow[k], actualRow[k]);
+                }
+            }
+        }
+
+        void writeComplete() {
+            mEncoder.writeComplete();
+        }
+
+    }
+
+    @Test
+    public void testOneNoteOn() throws IOException  {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x90, 0x40, 0x64
+                }};
+        EncoderChecker checker = new EncoderChecker();
+        checker.send(new byte[] {(byte) 0x90, 0x40, 0x64});
+        checker.compareWithExpected(encoded);
+    }
+
+    @Test
+    public void testTwoNoteOnsSameChannel() throws IOException  {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x90, 0x40, 0x64,
+                // encoder converts to running status
+                0x47, 0x72
+                }};
+        EncoderChecker checker = new EncoderChecker();
+        checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x72});
+        checker.compareWithExpected(encoded);
+    }
+
+    @Test
+    public void testTwoNoteOnsTwoChannels() throws IOException  {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x93, 0x40, 0x60,
+                // two channels so no running status
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x95, 0x47, 0x64
+                }};
+        EncoderChecker checker = new EncoderChecker();
+        checker.send(new byte[] {(byte) 0x93, 0x40, 0x60, (byte) 0x95, 0x47, 0x64});
+        checker.compareWithExpected(encoded);
+    }
+
+    @Test
+    public void testTwoNoteOnsOverTime() throws IOException  {
+        final byte[][] encoded = {
+                {
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // high bit of timestamp
+                (byte) 0x98, 0x45, 0x60
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x82, // timestamp advanced by 2 msec
+                (byte) 0x90, 0x40, 0x64,
+                (byte) 0x84, // timestamp needed because of time delay
+                // encoder converts to running status
+                0x47, 0x72
+                }};
+        EncoderChecker checker = new EncoderChecker();
+        long timestamp = 0;
+        // Send one note. This will cause an immediate packet write
+        // because we don't know when the next one will arrive.
+        checker.send(new byte[] {(byte) 0x98, 0x45, 0x60}, timestamp);
+
+        // Send two notes. These should accumulate into the
+        // same packet because we do not yet have a writeComplete.
+        timestamp += 2 * NANOS_PER_MSEC;
+        checker.send(new byte[] {(byte) 0x90, 0x40, 0x64}, timestamp);
+        timestamp += 2 * NANOS_PER_MSEC;
+        checker.send(new byte[] {(byte) 0x90, 0x47, 0x72}, timestamp);
+        // Tell the encoder that the first packet has been written to the
+        // hardware. So it can flush the two pending notes.
+        checker.writeComplete();
+        checker.compareWithExpected(encoded);
+    }
+
+    @Test
+    public void testSysExBasic() throws IOException  {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // timestamp
+                (byte) 0xF0, 0x7D, // Begin prototyping SysEx
+                0x01, 0x02, 0x03, 0x04, 0x05,
+                (byte) 0x80, // timestamp
+                (byte) 0xF7 // End SysEx
+                }};
+        EncoderChecker checker = new EncoderChecker();
+        checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx
+                0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xF7});
+        checker.compareWithExpected(encoded);
+    }
+
+    @Test
+    public void testSysExTwoPackets() throws IOException  {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // timestamp
+                (byte) 0xF0, 0x7D, // Begin prototyping SysEx
+                0x01, 0x02
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                0x03, 0x04, 0x05,
+                (byte) 0x80, // timestamp
+                (byte) 0xF7 // End SysEx
+                }};
+        EncoderChecker checker = new EncoderChecker();
+        // Send in two messages.
+        checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx
+                0x01, 0x02});
+        checker.send(new byte[] {0x03, 0x04, 0x05, (byte) 0xF7});
+        // Tell the encoder that the first packet has been written to the
+        // hardware. So it can flush the remaining data.
+        checker.writeComplete();
+        checker.compareWithExpected(encoded);
+    }
+
+    @Test
+    public void testSysExThreePackets() throws IOException  {
+        final byte[][] encoded = {{
+                (byte) 0x80, // high bit of header must be set
+                (byte) 0x80, // timestamp
+                (byte) 0xF0, 0x7D, // Begin prototyping SysEx
+                0x01, 0x02
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                0x03, 0x04, 0x05,
+                },
+                {
+                (byte) 0x80, // high bit of header must be set
+                0x06, 0x07, 0x08,
+                (byte) 0x80, // timestamp
+                (byte) 0xF7 // End SysEx
+                }};
+        EncoderChecker checker = new EncoderChecker();
+        // Send in three messages.
+        checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx
+                0x01, 0x02});
+        checker.send(new byte[] {0x03, 0x04, 0x05});
+        checker.writeComplete();
+        checker.send(new byte[] {0x06, 0x07, 0x08, (byte) 0xF7});
+        checker.writeComplete();
+        checker.compareWithExpected(encoded);
+    }
+}
diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/MidiFramerTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/MidiFramerTest.java
new file mode 100644
index 0000000..8cda6eb
--- /dev/null
+++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/MidiFramerTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.midi.MidiFramer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MidiFramerTest {
+
+    private static final String TAG = "MidiFramerTest";
+    private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+
+    // For testing MidiFramer
+    // TODO move MidiFramer tests to their own file
+    static class FramerChecker {
+        AccumulatingMidiReceiver mReceiver;
+        MidiFramer mFramer;
+
+        FramerChecker() {
+            mReceiver = new AccumulatingMidiReceiver();
+            mFramer = new MidiFramer(mReceiver);
+        }
+
+        void compareWithExpected(final byte[][] expected) {
+            byte[][] actualRows = mReceiver.getBuffers();
+            assertEquals(expected.length, actualRows.length);
+            // Compare the gathered rows with the expected rows.
+            for (int i = 0; i < expected.length; i++) {
+                byte[] expectedRow = expected[i];
+                Log.d(TAG, "expectedRow = "
+                        + MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length));
+                byte[] actualRow = actualRows[i];
+                Log.d(TAG, "actualRow = "
+                        + MidiFramer.formatMidiData(actualRow, 0, actualRow.length));
+                assertArrayEquals(expectedRow, actualRow);
+            }
+        }
+
+        void send(byte[] data) throws IOException {
+            Log.d(TAG, "send " + MidiFramer.formatMidiData(data, 0, data.length));
+            mFramer.send(data, 0, data.length, 0);
+        }
+    }
+
+    @Test
+    public void testFramerTwoNoteOns() throws IOException {
+        final byte[][] expected = {
+                {(byte) 0x90, 0x40, 0x64},
+                {(byte) 0x90, 0x47, 0x50}
+                };
+        FramerChecker checker = new FramerChecker();
+        checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x50});
+        checker.compareWithExpected(expected);
+    }
+
+    @Test
+    public void testFramerTwoNoteOnsRunning() throws IOException {
+        final byte[][] expected = {
+                {(byte) 0x90, 0x40, 0x64},
+                {(byte) 0x90, 0x47, 0x70}
+                };
+        FramerChecker checker = new FramerChecker();
+        // Two notes with running status
+        checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, 0x47, 0x70});
+        checker.compareWithExpected(expected);
+    }
+
+    @Test
+    public void testFramerPreGarbage() throws IOException {
+        final byte[][] expected = {
+                {(byte) 0x90, 0x40, 0x64},
+                {(byte) 0x90, 0x47, 0x70}
+                };
+        FramerChecker checker = new FramerChecker();
+        // Garbage can come before the first status byte if you connect
+        // a MIDI cable in the middle of a message.
+        checker.send(new byte[] {0x01, 0x02, // garbage bytes
+                (byte) 0x90, 0x40, 0x64, 0x47, 0x70});
+        checker.compareWithExpected(expected);
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 2fde87c..d3d04e5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2813,6 +2813,7 @@
                 if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) {
                     return false;
                 }
+                settingsState.unbanAllConfigIfBannedConfigUpdatedLocked(prefix);
                 List<String> changedSettings =
                         settingsState.setSettingsLocked(prefix, keyValues, packageName);
                 if (!changedSettings.isEmpty()) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index d93c015..6b8219e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -459,6 +459,16 @@
     }
 
     @GuardedBy("mLock")
+    public void unbanAllConfigIfBannedConfigUpdatedLocked(String prefix) {
+        // If the prefix updated is a banned namespace, clear mNamespaceBannedHashes
+        // to unban all unbanned namespaces.
+        if (mNamespaceBannedHashes.get(prefix) != null) {
+            mNamespaceBannedHashes.clear();
+            scheduleWriteIfNeededLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
     public void banConfigurationLocked(String prefix, Map<String, String> keyValues) {
         if (prefix == null || keyValues.isEmpty()) {
             return;
diff --git a/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml
new file mode 100644
index 0000000..8f8f1b6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M206,150m-150,0a150,150 0,1 1,300 0a150,150 0,1 1,-300 0"/>
+    <path
+        android:pathData="M296,105.2h-9.6l-3.1,-2.5l-3.1,2.5H116c-1.7,0 -3,1.3 -3,3v111.7c0,1.7 1.3,3 3,3h180c1.7,0 3,-1.3 3,-3V108.2C299,106.6 297.7,105.2 296,105.2C296,105.2 296,105.2 296,105.2z"
+        android:fillColor="#3C4043"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M252.4,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#D2E3FC"
+        android:strokeColor="#4285F4"/>
+    <path
+        android:pathData="M261.9,95.7m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"
+        android:fillColor="#4285F4"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M160.6,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#FAD2CF"
+        android:strokeColor="#EA4335"/>
+    <path
+        android:pathData="M170.1,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0"
+        android:fillColor="#EA4335"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M192.1,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#FEEFC3"
+        android:strokeColor="#FBBC04"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M221.8,85.4m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#CEEAD6"
+        android:strokeColor="#34A853"/>
+    <path
+        android:pathData="M201.6,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0"
+        android:fillColor="#FBBC04"/>
+    <path
+        android:pathData="M231.4,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0"
+        android:fillColor="#34A853"/>
+    <path
+        android:pathData="M282.8,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#3C4043"/>
+    <path
+        android:pathData="M278.7,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M282.8,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M286.9,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M129.2,72.9h-3.4l0.3,1c1,-0.3 2.1,-0.4 3.2,-0.4v-1V72.9zM122.6,74.8c-0.5,0.3 -1,0.6 -1.4,1l0,0l0,0l0,0l0,0h-0.6l0,0l0,0l0,0l0,0l0,0l0,0c-0.2,0.2 -0.3,0.3 -0.4,0.5l0.8,0.7c0.7,-0.8 1.5,-1.5 2.4,-2.1l-0.5,-0.8L122.6,74.8zM118,80L118,80L118,80L118,80L118,80L118,80L118,80L118,80c-0.5,1 -0.8,2 -1,3l1,0.2c0.2,-1 0.5,-2 1,-3L118,80zM117.8,86.7l-1,0.1c0.1,0.6 0.2,1.1 0.3,1.7l0,0l0,0h0.1l0,0l0,0l0,0l0,0c0.1,0.5 0.3,0.9 0.5,1.4l0.9,-0.4c-0.4,-1 -0.7,-2 -0.8,-3.1L117.8,86.7zM120.2,92.5l-0.8,0.6l0.2,0.3l0,0l0,0l0,0l0,0h0.3l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0.4,0.4l0,0l0,0l0,0l0,0h0.1l0,0l0,0l0,0l0,0l0,0h0.5l0,0l0,0l0,0l0,0l0,0l0,0l0.6,0.4l0.6,-0.8c-0.9,-0.6 -1.7,-1.4 -2.3,-2.2L120.2,92.5zM125.4,96.2l-0.3,0.9c1.1,0.4 2.2,0.6 3.4,0.7l0.1,-1C127.5,96.7 126.4,96.5 125.4,96.2zM134.7,95.4c-0.9,0.5 -2,0.9 -3,1.1l0.2,1h0.4c1,-0.3 2,-0.6 2.9,-1.2l-0.5,-0.9L134.7,95.4zM139.2,90.9c-0.5,0.9 -1.2,1.8 -1.9,2.5l0.7,0.7v-0.1h0.2l0,0l0,0c0.7,-0.7 1.3,-1.6 1.8,-2.4l-0.9,-0.5L139.2,90.9zM141.6,84.7h-1c0,0.2 0,0.4 0,0.6c0,0.9 -0.1,1.7 -0.3,2.6l1,0.2c0.1,-0.4 0.2,-0.8 0.2,-1.2l0,0v-0.1l0,0v-0.1l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,-0.2 0,-0.3 0,-0.5l0,0L141.6,84.7zM139.3,78.2l-0.8,0.6c0.6,0.9 1.1,1.8 1.5,2.8l0.9,-0.3c-0.1,-0.2 -0.2,-0.4 -0.2,-0.7l0,0l0,0h-0.1l0,0l0,0l0,0l0,0l0,0l0,0c-0.3,-0.7 -0.7,-1.4 -1.1,-2l0,0l0,0l0,0l0,0l0,0l0,0l0,0L139.3,78.2zM134,73.9l-0.4,0.9c1,0.4 1.9,1 2.7,1.6l0.6,-0.8l0,0l0,0l0,0l0,0l0,0c-0.3,-0.3 -0.7,-0.5 -1,-0.7l0,0h-0.1h-0.6c-0.4,-0.2 -0.8,-0.4 -1.2,-0.6L134,73.9zM129.2,72.9v1c0.4,0 0.9,0 1.3,0.1l0.1,-1l-0.9,-0.1L129.2,72.9L129.2,72.9z"
+        android:fillColor="#34A853"/>
+    <path
+        android:pathData="M206,252m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0"
+        android:fillColor="#F1F3F4"/>
+    <path
+        android:pathData="M201.7,247.7L210.3,256.3"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#202124"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M210.3,247.7L201.7,256.3"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#202124"
+        android:strokeLineCap="round"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M205.3,221.9m-10.4,0a10.4,10.4 0,1 1,20.8 0a10.4,10.4 0,1 1,-20.8 0"
+        android:fillColor="#CEEAD6"
+        android:strokeColor="#34A853"/>
+    <path
+        android:pathData="M481.4,292.2c48,58.3 119.8,125.8 58.6,162.9c-38.7,23.5 -53.9,24 -98.3,33.2c-43.8,9.1 -93.6,-89.8 -101.1,-134.5C329.6,288.6 452.6,257.2 481.4,292.2z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M458.2,320.7l-21.1,-71.4L400.5,193c-2.7,-5.1 -1.2,-11.4 3.5,-14.7l0,0c2.8,-2 6.6,-1.5 8.8,1.1c0,0 40.6,38.4 53.2,61.1l81.5,134.8l-69.9,-39.1L458.2,320.7z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M403.8,184.8l5.4,6.9c1.2,1.5 3.3,1.9 4.9,0.9l3,-1.8"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M420.9,325.8l-37.8,-88.6l-58.4,-37.8c-5.7,-5.4 -7.4,-13.8 -4.2,-21l0,0c2,-4.6 7.4,-6.7 12,-4.6c0.2,0.1 0.4,0.2 0.7,0.3c0,0 70.7,36.3 81.5,48.3l59.8,95.5l-49.9,24.9L420.9,325.8z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M324.6,183.9l8,6.2c2.1,1.7 5.2,1.4 7,-0.6l2.9,-3.3"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M392.4,231c3.8,-5.1 9.1,-8.9 15.1,-10.9"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+    <path
+        android:pathData="M401.3,283.8L405.8,292.6"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+    <path
+        android:pathData="M378.2,346.9l-34.7,-75.6l-60,-61.2c-6.3,-4.7 -9,-12.8 -6.7,-20.4l0,0c1.5,-4.8 6.5,-7.5 11.3,-6c0.2,0.1 0.4,0.1 0.7,0.2c0,0 73.5,48.2 82.6,61.7l64.1,95.7l-40.3,23.5L378.2,346.9z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M280.8,196.6l7.6,4.6c2.6,1.6 5.9,1.1 7.9,-1.1l4.1,-4.5"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M347.5,251c3.8,-5.1 9.1,-8.9 15.1,-10.9"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+    <path
+        android:pathData="M207.2,234.1c-8.8,-11 4.7,-31.5 19.8,-19c17.7,14.7 74.7,64.3 74.7,64.3l103.8,101.8c0,0 -36.4,53.8 -44.5,42.3C287.8,319.3 234.4,267.9 207.2,234.1z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M209.6,226.2l9.3,9.5c1,0.8 3,0.4 3.1,-1c0.2,-2.2 4.6,-6.2 7,-6.6c1.1,-0.3 1.7,-1.4 1.4,-2.4c-0.1,-0.2 -0.2,-0.4 -0.3,-0.6l-4.4,-3.9"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M284.1,296.2c3.1,-5.5 7.8,-10 13.5,-12.8"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+  </group>
+  <path
+      android:pathData="M206,4c80.6,0 146,65.4 146,146c0,38.7 -15.4,75.9 -42.8,103.2c-57,57 -149.5,57 -206.5,0s-57,-149.5 0,-206.5C130.1,19.3 167.3,3.9 206,4M206,0C123.2,0 56,67.2 56,150s67.2,150 150,150s150,-67.2 150,-150S288.8,0 206,0z"
+      android:fillColor="#D2E3FC"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml
new file mode 100644
index 0000000..5e02f67
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M206,150m-150,0a150,150 0,1 1,300 0a150,150 0,1 1,-300 0"/>
+    <path
+        android:pathData="M296,105.2h-9.6l-3.1,-2.5l-3.1,2.5H116c-1.7,0 -3,1.3 -3,3v111.7c0,1.7 1.3,3 3,3h180c1.7,0 3,-1.3 3,-3V108.2C299,106.6 297.7,105.2 296,105.2L296,105.2z"
+        android:fillColor="#F1F3F4"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M252.4,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#D2E3FC"
+        android:strokeColor="#4285F4"/>
+    <path
+        android:pathData="M261.9,95.7m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"
+        android:fillColor="#4285F4"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M160.6,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#FAD2CF"
+        android:strokeColor="#EA4335"/>
+    <path
+        android:pathData="M170.1,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0"
+        android:fillColor="#EA4335"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M192.1,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#FEEFC3"
+        android:strokeColor="#FBBC04"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M221.8,85.4m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#CEEAD6"
+        android:strokeColor="#34A853"/>
+    <path
+        android:pathData="M201.6,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0"
+        android:fillColor="#FBBC04"/>
+    <path
+        android:pathData="M231.4,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0"
+        android:fillColor="#34A853"/>
+    <path
+        android:pathData="M282.8,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
+        android:fillColor="#F1F3F4"/>
+    <path
+        android:pathData="M278.7,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0"
+        android:fillColor="#3474E0"/>
+    <path
+        android:pathData="M282.8,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0"
+        android:fillColor="#3474E0"/>
+    <path
+        android:pathData="M286.9,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0"
+        android:fillColor="#3474E0"/>
+    <path
+        android:pathData="M129.2,72.9h-3.4l0.3,1c1,-0.3 2.1,-0.4 3.2,-0.4v-1v0.4H129.2zM122.6,74.8c-0.5,0.3 -1,0.6 -1.4,1l0,0l0,0l0,0l0,0h-0.6l0,0l0,0l0,0l0,0l0,0l0,0c-0.2,0.2 -0.3,0.3 -0.4,0.5L121,77c0.7,-0.8 1.5,-1.5 2.4,-2.1l-0.5,-0.8L122.6,74.8zM118,80L118,80L118,80L118,80L118,80L118,80L118,80L118,80c-0.5,1 -0.8,2 -1,3l1,0.2c0.2,-1 0.5,-2 1,-3L118,80zM117.8,86.7l-1,0.1c0.1,0.6 0.2,1.1 0.3,1.7l0,0l0,0h0.1l0,0l0,0l0,0l0,0c0.1,0.5 0.3,0.9 0.5,1.4l0.9,-0.4c-0.4,-1 -0.7,-2 -0.8,-3.1V86.7zM120.2,92.5l-0.8,0.6l0.2,0.3l0,0l0,0l0,0l0,0h0.3l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0.4,0.4l0,0l0,0l0,0l0,0h0.1l0,0l0,0l0,0l0,0l0,0h0.5l0,0l0,0l0,0l0,0l0,0l0,0l0.6,0.4l0.6,-0.8c-0.9,-0.6 -1.7,-1.4 -2.3,-2.2L120.2,92.5zM125.4,96.2l-0.3,0.9c1.1,0.4 2.2,0.6 3.4,0.7l0.1,-1C127.5,96.7 126.4,96.5 125.4,96.2zM134.7,95.4c-0.9,0.5 -2,0.9 -3,1.1l0.2,1h0.4c1,-0.3 2,-0.6 2.9,-1.2L134.7,95.4L134.7,95.4zM139.2,90.9c-0.5,0.9 -1.2,1.8 -1.9,2.5l0.7,0.7V94h0.2l0,0l0,0c0.7,-0.7 1.3,-1.6 1.8,-2.4l-0.9,-0.5L139.2,90.9zM141.6,84.7h-1c0,0.2 0,0.4 0,0.6c0,0.9 -0.1,1.7 -0.3,2.6l1,0.2c0.1,-0.4 0.2,-0.8 0.2,-1.2l0,0v-0.1l0,0v-0.1l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,-0.2 0,-0.3 0,-0.5l0,0L141.6,84.7zM139.3,78.2l-0.8,0.6c0.6,0.9 1.1,1.8 1.5,2.8l0.9,-0.3c-0.1,-0.2 -0.2,-0.4 -0.2,-0.7l0,0l0,0h-0.1l0,0l0,0l0,0l0,0l0,0l0,0c-0.3,-0.7 -0.7,-1.4 -1.1,-2l0,0l0,0l0,0l0,0l0,0l0,0l0,0L139.3,78.2zM134,73.9l-0.4,0.9c1,0.4 1.9,1 2.7,1.6l0.6,-0.8l0,0l0,0l0,0l0,0l0,0c-0.3,-0.3 -0.7,-0.5 -1,-0.7l0,0h-0.1h-0.6c-0.4,-0.2 -0.8,-0.4 -1.2,-0.6V73.9zM129.2,72.9v1c0.4,0 0.9,0 1.3,0.1l0.1,-1l-0.9,-0.1H129.2L129.2,72.9z"
+        android:fillColor="#34A853"/>
+    <path
+        android:pathData="M206,252m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0"
+        android:fillColor="#9AA0A6"/>
+    <path
+        android:pathData="M201.7,247.7L210.3,256.3"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#F1F3F4"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M210.3,247.7L201.7,256.3"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#F1F3F4"
+        android:strokeLineCap="round"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M205.3,221.9m-10.4,0a10.4,10.4 0,1 1,20.8 0a10.4,10.4 0,1 1,-20.8 0"
+        android:fillColor="#CEEAD6"
+        android:strokeColor="#34A853"/>
+    <path
+        android:pathData="M481.4,292.2c48,58.3 119.8,125.8 58.6,162.9c-38.7,23.5 -53.9,24 -98.3,33.2c-43.8,9.1 -93.6,-89.8 -101.1,-134.5C329.6,288.6 452.6,257.2 481.4,292.2z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M458.2,320.7l-21.1,-71.4L400.5,193c-2.7,-5.1 -1.2,-11.4 3.5,-14.7l0,0c2.8,-2 6.6,-1.5 8.8,1.1c0,0 40.6,38.4 53.2,61.1l81.5,134.8l-69.9,-39.1L458.2,320.7z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M403.8,184.8l5.4,6.9c1.2,1.5 3.3,1.9 4.9,0.9l3,-1.8"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M420.9,325.8l-37.8,-88.6l-58.4,-37.8c-5.7,-5.4 -7.4,-13.8 -4.2,-21l0,0c2,-4.6 7.4,-6.7 12,-4.6c0.2,0.1 0.4,0.2 0.7,0.3c0,0 70.7,36.3 81.5,48.3l59.8,95.5l-49.9,24.9L420.9,325.8z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M324.6,183.9l8,6.2c2.1,1.7 5.2,1.4 7,-0.6l2.9,-3.3"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M392.4,231c3.8,-5.1 9.1,-8.9 15.1,-10.9"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+    <path
+        android:pathData="M401.3,283.8L405.8,292.6"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+    <path
+        android:pathData="M378.2,346.9l-34.7,-75.6l-60,-61.2c-6.3,-4.7 -9,-12.8 -6.7,-20.4l0,0c1.5,-4.8 6.5,-7.5 11.3,-6c0.2,0.1 0.4,0.1 0.7,0.2c0,0 73.5,48.2 82.6,61.7l64.1,95.7l-40.3,23.5L378.2,346.9z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M280.8,196.6l7.6,4.6c2.6,1.6 5.9,1.1 7.9,-1.1l4.1,-4.5"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M347.5,251c3.8,-5.1 9.1,-8.9 15.1,-10.9"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+    <path
+        android:pathData="M207.2,234.1c-8.8,-11 4.7,-31.5 19.8,-19c17.7,14.7 74.7,64.3 74.7,64.3l103.8,101.8c0,0 -36.4,53.8 -44.5,42.3C287.8,319.3 234.4,267.9 207.2,234.1z"
+        android:fillColor="#D2E3FC"/>
+    <path
+        android:pathData="M209.6,226.2l9.3,9.5c1,0.8 3,0.4 3.1,-1c0.2,-2.2 4.6,-6.2 7,-6.6c1.1,-0.3 1.7,-1.4 1.4,-2.4c-0.1,-0.2 -0.2,-0.4 -0.3,-0.6l-4.4,-3.9"
+        android:strokeLineJoin="bevel"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"
+        android:strokeLineCap="square"/>
+    <path
+        android:pathData="M284.1,296.2c3.1,-5.5 7.8,-10 13.5,-12.8"
+        android:strokeWidth="1.75"
+        android:fillColor="#00000000"
+        android:strokeColor="#A0C2F9"/>
+  </group>
+  <path
+      android:pathData="M206,4c80.6,0 146,65.4 146,146c0,38.7 -15.4,75.9 -42.8,103.2c-57,57 -149.5,57 -206.5,0s-57,-149.5 0,-206.5C130.1,19.3 167.3,3.9 206,4M206,0C123.2,0 56,67.2 56,150s67.2,150 150,150s150,-67.2 150,-150S288.8,0 206,0z"
+      android:fillColor="#D2E3FC"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
index 65b04fd..3060619 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
@@ -20,6 +20,8 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingTop="@dimen/bubble_overflow_padding"
+    android:paddingLeft="@dimen/bubble_overflow_padding"
+    android:paddingRight="@dimen/bubble_overflow_padding"
     android:orientation="vertical"
     android:layout_gravity="center_horizontal">
 
@@ -39,6 +41,13 @@
         android:orientation="vertical"
         android:gravity="center">
 
+        <ImageView
+            android:layout_width="@dimen/bubble_empty_overflow_image_height"
+            android:layout_height="@dimen/bubble_empty_overflow_image_height"
+            android:id="@+id/bubble_overflow_empty_state_image"
+            android:scaleType="fitCenter"
+            android:layout_gravity="center"/>
+
         <TextView
             android:id="@+id/bubble_overflow_empty_title"
             android:text="@string/bubble_overflow_empty_title"
@@ -57,6 +66,7 @@
             android:text="@string/bubble_overflow_empty_subtitle"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/bubble_empty_overflow_subtitle_padding"
             android:gravity="center"/>
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml
index d67c81d..88a05ec 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_view.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml
@@ -37,5 +37,6 @@
         android:layout_height="wrap_content"
         android:maxLines="1"
         android:layout_gravity="center"
+        android:paddingTop="@dimen/bubble_overflow_text_padding"
         android:gravity="center"/>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index 7dcc46c..ec00429 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -43,6 +43,7 @@
             android:forceHasOverlappingRendering="false">
 
             <TextView
+                android:id="@+id/header_label"
                 style="@style/TextAppearance.NotificationSectionHeaderButton"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
index fe8557b..fc3bf94 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -136,6 +136,47 @@
             </LinearLayout>
         </LinearLayout>
 
+        <!-- Seek Bar -->
+        <SeekBar
+            android:id="@+id/media_progress_bar"
+            android:clickable="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:maxHeight="3dp"
+            android:paddingTop="24dp"
+            android:paddingBottom="24dp"
+            android:layout_marginBottom="-24dp"
+            android:layout_marginTop="-24dp"
+            android:splitTrack="false"
+        />
+
+        <FrameLayout
+            android:id="@+id/notification_media_progress_time"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            >
+            <!-- width is set to "match_parent" to avoid extra layout calls -->
+            <TextView
+                android:id="@+id/media_elapsed_time"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:fontFamily="@*android:string/config_bodyFontFamily"
+                android:textSize="14sp"
+                android:gravity="left"
+            />
+            <TextView
+                android:id="@+id/media_total_time"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:fontFamily="@*android:string/config_bodyFontFamily"
+                android:layout_alignParentRight="true"
+                android:textSize="14sp"
+                android:gravity="right"
+            />
+        </FrameLayout>
+
         <!-- Controls -->
         <LinearLayout
             android:id="@+id/media_actions"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a9e5fa9..5b213ed 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1148,11 +1148,17 @@
     <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
     <dimen name="bubble_expanded_default_height">180dp</dimen>
     <!-- Default height of bubble overflow -->
-    <dimen name="bubble_overflow_height">380dp</dimen>
+    <dimen name="bubble_overflow_height">460dp</dimen>
     <!-- Bubble overflow padding when there are no bubbles  -->
     <dimen name="bubble_overflow_empty_state_padding">16dp</dimen>
     <!-- Padding of container for overflow bubbles -->
-    <dimen name="bubble_overflow_padding">5dp</dimen>
+    <dimen name="bubble_overflow_padding">15dp</dimen>
+    <!-- Padding of label for bubble overflow view -->
+    <dimen name="bubble_overflow_text_padding">7dp</dimen>
+    <!-- Height of bubble overflow empty state illustration -->
+    <dimen name="bubble_empty_overflow_image_height">200dp</dimen>
+    <!-- Padding of bubble overflow empty state subtitle -->
+    <dimen name="bubble_empty_overflow_subtitle_padding">50dp</dimen>
     <!-- Height of the triangle that points to the expanded bubble -->
     <dimen name="bubble_pointer_height">4dp</dimen>
     <!-- Width of the triangle that points to the expanded bubble -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 80fd826..35ad422 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -26,7 +26,7 @@
 
 /**
  * Temporary callbacks into SystemUI.
- * Next id = 25
+ * Next id = 26
  */
 interface ISystemUiProxy {
 
@@ -140,4 +140,10 @@
      * Sets listener to get pinned stack animation callbacks.
      */
     void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) = 24;
+
+    /**
+     * Notifies that quickstep will switch to a new task
+     * @param rotation indicates which Surface.Rotation the gesture was started in
+     */
+    void onQuickSwitchToNewTask(int rotation) = 25;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
index 29100ef8..8bd7c79 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Size;
@@ -59,6 +60,7 @@
         if (mSurfaceControlViewHost != null) {
             mSurfaceControlViewHost.die();
         }
+
         SurfaceControl surfaceControl = SurfaceViewRequestUtils.getSurfaceControl(bundle);
         if (surfaceControl != null) {
             if (viewSize == null) {
@@ -70,8 +72,10 @@
             WindowlessWindowManager windowlessWindowManager =
                     new WindowlessWindowManager(context.getResources().getConfiguration(),
                             surfaceControl, hostToken);
+            DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
             mSurfaceControlViewHost = new SurfaceControlViewHost(context,
-                    context.getDisplayNoVerify(), windowlessWindowManager);
+                    dm.getDisplay(SurfaceViewRequestUtils.getDisplayId(bundle)),
+                    windowlessWindowManager);
             WindowManager.LayoutParams layoutParams =
                     new WindowManager.LayoutParams(
                             viewSize.getWidth(),
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java
index 4409276..6742a4dc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java
@@ -26,30 +26,38 @@
 public class SurfaceViewRequestUtils {
     private static final String KEY_HOST_TOKEN = "host_token";
     private static final String KEY_SURFACE_CONTROL = "surface_control";
+    private static final String KEY_DISPLAY_ID = "display_id";
 
     /** Creates a SurfaceView based bundle that stores the input host token and surface control. */
     public static Bundle createSurfaceBundle(SurfaceView surfaceView) {
         Bundle bundle = new Bundle();
         bundle.putBinder(KEY_HOST_TOKEN, surfaceView.getHostToken());
         bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl());
+        bundle.putInt(KEY_DISPLAY_ID, surfaceView.getDisplay().getDisplayId());
         return bundle;
     }
 
     /**
      * Retrieves the SurfaceControl from a bundle created by
      * {@link #createSurfaceBundle(SurfaceView)}.
-     **/
+     */
     public static SurfaceControl getSurfaceControl(Bundle bundle) {
         return bundle.getParcelable(KEY_SURFACE_CONTROL);
     }
 
     /**
-     * Retrieves the input token from a bundle created by
-     * {@link #createSurfaceBundle(SurfaceView)}.
-     **/
+     * Retrieves the input token from a bundle created by {@link #createSurfaceBundle(SurfaceView)}.
+     */
     public static @Nullable IBinder getHostToken(Bundle bundle) {
         return bundle.getBinder(KEY_HOST_TOKEN);
     }
 
+    /**
+     * Retrieves the display id from a bundle created by {@link #createSurfaceBundle(SurfaceView)}.
+     */
+    public static int getDisplayId(Bundle bundle) {
+        return bundle.getInt(KEY_DISPLAY_ID);
+    }
+
     private SurfaceViewRequestUtils() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 2231d11..37841f2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -21,18 +21,17 @@
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.app.Activity;
-import android.app.Notification;
-import android.app.Person;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.os.Bundle;
-import android.os.Parcelable;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -55,6 +54,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
 
     private LinearLayout mEmptyState;
+    private ImageView mEmptyStateImage;
     private BubbleController mBubbleController;
     private BubbleOverflowAdapter mAdapter;
     private RecyclerView mRecyclerView;
@@ -73,6 +73,7 @@
 
         mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
         mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
+        mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
 
         Resources res = getResources();
         final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
@@ -81,11 +82,15 @@
 
         DisplayMetrics displayMetrics = new DisplayMetrics();
         getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
-        final int viewWidth = displayMetrics.widthPixels / columns;
+        final int recyclerViewWidth = (displayMetrics.widthPixels
+                - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding));
+        final int viewWidth = recyclerViewWidth / columns;
 
         final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
         final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns);
-        final int viewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height) / rows;
+        final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height)
+                - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
+        final int viewHeight = recyclerViewHeight / rows;
 
         mAdapter = new BubbleOverflowAdapter(mOverflowBubbles,
                 mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
@@ -94,6 +99,31 @@
         mBubbleController.setOverflowCallback(() -> {
             onDataChanged(mBubbleController.getOverflowBubbles());
         });
+        onThemeChanged();
+    }
+
+    /**
+     * Handle theme changes.
+     */
+    void onThemeChanged() {
+        final int mode =
+                getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        switch (mode) {
+            case Configuration.UI_MODE_NIGHT_NO:
+                if (DEBUG_OVERFLOW) {
+                    Log.d(TAG, "Set overflow UI to light mode");
+                }
+                mEmptyStateImage.setImageDrawable(
+                        getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light));
+                break;
+            case Configuration.UI_MODE_NIGHT_YES:
+                if (DEBUG_OVERFLOW) {
+                    Log.d(TAG, "Set overflow UI to dark mode");
+                }
+                mEmptyStateImage.setImageDrawable(
+                        getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark));
+                break;
+        }
     }
 
     void setBackgroundColor() {
@@ -134,6 +164,7 @@
     @Override
     public void onResume() {
         super.onResume();
+        onThemeChanged();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 6a7b0da..eff6934 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -713,7 +713,7 @@
         } else {
             mBubbleContainer.removeView(mBubbleOverflow.getBtn());
             mBubbleOverflow.updateIcon(mContext, this);
-            overflowBtnIndex = mBubbleContainer.getChildCount() - 1;
+            overflowBtnIndex = mBubbleContainer.getChildCount();
         }
         mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 6d99ef1..fdd8593 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -78,10 +78,13 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.drawable.ScrimDrawable;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.EmergencyAffordanceManager;
@@ -171,6 +174,7 @@
     private final IActivityManager mIActivityManager;
     private final TelecomManager mTelecomManager;
     private final MetricsLogger mMetricsLogger;
+    private final UiEventLogger mUiEventLogger;
     private final NotificationShadeDepthController mDepthController;
     private final BlurUtils mBlurUtils;
 
@@ -203,6 +207,23 @@
     private final ControlsListingController mControlsListingController;
     private boolean mAnyControlsProviders = false;
 
+    @VisibleForTesting
+    public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
+        GA_POWER_MENU_OPEN(337);
+
+        private final int mId;
+
+        GlobalActionsEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
     /**
      * @param context everything needs a context :(
      */
@@ -223,7 +244,7 @@
             ControlsUiController controlsUiController, IWindowManager iWindowManager,
             @Background Executor backgroundExecutor,
             ControlsListingController controlsListingController,
-            ControlsController controlsController) {
+            ControlsController controlsController, UiEventLogger uiEventLogger) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -240,6 +261,7 @@
         mIActivityManager = iActivityManager;
         mTelecomManager = telecomManager;
         mMetricsLogger = metricsLogger;
+        mUiEventLogger = uiEventLogger;
         mDepthController = depthController;
         mSysuiColorExtractor = colorExtractor;
         mStatusBarService = statusBarService;
@@ -997,6 +1019,7 @@
      */
     public void onShow(DialogInterface dialog) {
         mMetricsLogger.visible(MetricsEvent.POWER_MENU);
+        mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
new file mode 100644
index 0000000..aa5ebaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.media
+
+import android.content.res.ColorStateList
+import android.text.format.DateUtils
+import android.view.View
+import android.widget.SeekBar
+import android.widget.TextView
+import androidx.annotation.UiThread
+import androidx.lifecycle.Observer
+
+import com.android.systemui.R
+
+/**
+ * Observer for changes from SeekBarViewModel.
+ *
+ * <p>Updates the seek bar views in response to changes to the model.
+ */
+class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> {
+
+    private val seekBarView: SeekBar
+    private val elapsedTimeView: TextView
+    private val totalTimeView: TextView
+
+    init {
+        seekBarView = view.findViewById(R.id.media_progress_bar)
+        elapsedTimeView = view.findViewById(R.id.media_elapsed_time)
+        totalTimeView = view.findViewById(R.id.media_total_time)
+    }
+
+    /** 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)
+            seekBarView.setThumbTintList(tintList)
+            tintList = tintList.withAlpha(192) // 75%
+            seekBarView.setProgressTintList(tintList)
+            tintList = tintList.withAlpha(128) // 50%
+            seekBarView.setProgressBackgroundTintList(tintList)
+            elapsedTimeView.setTextColor(it)
+            totalTimeView.setTextColor(it)
+        }
+
+        data.elapsedTime?.let {
+            seekBarView.setProgress(it)
+            elapsedTimeView.setText(DateUtils.formatElapsedTime(
+                    it / DateUtils.SECOND_IN_MILLIS))
+        }
+
+        data.duration?.let {
+            seekBarView.setMax(it)
+            totalTimeView.setText(DateUtils.formatElapsedTime(
+                    it / DateUtils.SECOND_IN_MILLIS))
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
new file mode 100644
index 0000000..cf8f268
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.media
+
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.view.MotionEvent
+import android.view.View
+import android.widget.SeekBar
+import androidx.annotation.AnyThread
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.LiveData
+
+import com.android.systemui.util.concurrency.DelayableExecutor
+
+private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L
+
+/** ViewModel for seek bar in QS media player. */
+class SeekBarViewModel(val bgExecutor: DelayableExecutor) {
+
+    private val _progress = MutableLiveData<Progress>().apply {
+        postValue(Progress(false, false, null, null, null))
+    }
+    val progress: LiveData<Progress>
+        get() = _progress
+    private var controller: MediaController? = null
+    private var playbackState: PlaybackState? = null
+
+    /** Listening state (QS open or closed) is used to control polling of progress. */
+    var listening = true
+        set(value) {
+            if (value) {
+                checkPlaybackPosition()
+            }
+        }
+
+    /**
+     * Handle request to change the current position in the media track.
+     * @param position Place to seek to in the track.
+     */
+    @WorkerThread
+    fun onSeek(position: Long) {
+        controller?.transportControls?.seekTo(position)
+    }
+
+    /**
+     * Updates media information.
+     * @param mediaController controller for media session
+     * @param color foreground color for UI elements
+     */
+    @WorkerThread
+    fun updateController(mediaController: MediaController?, color: Int) {
+        controller = mediaController
+        playbackState = controller?.playbackState
+        val mediaMetadata = controller?.metadata
+        val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
+        val position = playbackState?.position?.toInt()
+        val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt()
+        val enabled = if (duration != null && duration <= 0) false else true
+        _progress.postValue(Progress(enabled, seekAvailable, position, duration, color))
+        if (shouldPollPlaybackPosition()) {
+            checkPlaybackPosition()
+        }
+    }
+
+    @AnyThread
+    private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({
+        val currentPosition = controller?.playbackState?.position?.toInt()
+        if (currentPosition != null && _progress.value!!.elapsedTime != currentPosition) {
+            _progress.postValue(_progress.value!!.copy(elapsedTime = currentPosition))
+        }
+        if (shouldPollPlaybackPosition()) {
+            checkPlaybackPosition()
+        }
+    }, POSITION_UPDATE_INTERVAL_MILLIS)
+
+    @WorkerThread
+    private fun shouldPollPlaybackPosition(): Boolean {
+        val state = playbackState?.state
+        val moving = if (state == null) false else
+                state == PlaybackState.STATE_PLAYING ||
+                state == PlaybackState.STATE_BUFFERING ||
+                state == PlaybackState.STATE_FAST_FORWARDING ||
+                state == PlaybackState.STATE_REWINDING
+        return moving && listening
+    }
+
+    /** Gets a listener to attach to the seek bar to handle seeking. */
+    val seekBarListener: SeekBar.OnSeekBarChangeListener
+        get() {
+            return SeekBarChangeListener(this, bgExecutor)
+        }
+
+    /** Gets a listener to attach to the seek bar to disable touch intercepting. */
+    val seekBarTouchListener: View.OnTouchListener
+        get() {
+            return SeekBarTouchListener()
+        }
+
+    private class SeekBarChangeListener(
+        val viewModel: SeekBarViewModel,
+        val bgExecutor: DelayableExecutor
+    ) : SeekBar.OnSeekBarChangeListener {
+        override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
+            if (fromUser) {
+                bgExecutor.execute {
+                    viewModel.onSeek(progress.toLong())
+                }
+            }
+        }
+        override fun onStartTrackingTouch(bar: SeekBar) {
+        }
+        override fun onStopTrackingTouch(bar: SeekBar) {
+            val pos = bar.progress.toLong()
+            bgExecutor.execute {
+                viewModel.onSeek(pos)
+            }
+        }
+    }
+
+    private class SeekBarTouchListener : View.OnTouchListener {
+        override fun onTouch(view: View, event: MotionEvent): Boolean {
+            view.parent.requestDisallowInterceptTouchEvent(true)
+            return view.onTouchEvent(event)
+        }
+    }
+
+    /** State seen by seek bar UI. */
+    data class Progress(
+        val enabled: Boolean,
+        val seekAvailable: Boolean,
+        val elapsedTime: Int?,
+        val duration: Int?,
+        val color: Int?
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 15c9dba..8cff20a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -37,7 +37,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.RemoteException;
 import android.util.Log;
 import android.util.Size;
 import android.view.SurfaceControl;
@@ -541,7 +540,12 @@
             return null;
         }
         final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
-        return new Size(windowLayout.minWidth, windowLayout.minHeight);
+        // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
+        // without minWidth/minHeight
+        if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
+            return new Size(windowLayout.minWidth, windowLayout.minHeight);
+        }
+        return null;
     }
 
     private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 8922e14..339a408 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -16,11 +16,14 @@
 
 package com.android.systemui.qs;
 
+import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
+
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.util.Log;
 import android.view.View;
@@ -28,12 +31,16 @@
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.SeekBar;
 import android.widget.TextView;
 
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.R;
 import com.android.systemui.media.MediaControlPanel;
+import com.android.systemui.media.SeekBarObserver;
+import com.android.systemui.media.SeekBarViewModel;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.concurrent.Executor;
 
@@ -54,6 +61,9 @@
     };
 
     private final QSPanel mParent;
+    private final DelayableExecutor mBackgroundExecutor;
+    private final SeekBarViewModel mSeekBarViewModel;
+    private final SeekBarObserver mSeekBarObserver;
 
     /**
      * Initialize quick shade version of player
@@ -64,10 +74,20 @@
      * @param backgroundExecutor
      */
     public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
-            Executor foregroundExecutor, Executor backgroundExecutor) {
+            Executor foregroundExecutor, DelayableExecutor backgroundExecutor) {
         super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, foregroundExecutor,
                 backgroundExecutor);
         mParent = (QSPanel) parent;
+        mBackgroundExecutor = backgroundExecutor;
+        mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
+        mSeekBarObserver = new SeekBarObserver(getView());
+        // Can't use the viewAttachLifecycle of media player because remove/add is used to adjust
+        // priority of players. As soon as it is removed, the lifecycle will end and the seek bar
+        // will stop updating. So, use the lifecycle of the parent instead.
+        mSeekBarViewModel.getProgress().observe(viewAttachLifecycle(parent), mSeekBarObserver);
+        SeekBar bar = getView().findViewById(R.id.media_progress_bar);
+        bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
+        bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
     }
 
     /**
@@ -115,6 +135,11 @@
             thisBtn.setVisibility(View.GONE);
         }
 
+        // Seek Bar
+        final MediaController controller = new MediaController(getContext(), token);
+        mBackgroundExecutor.execute(
+                () -> mSeekBarViewModel.updateController(controller, iconColor));
+
         // Set up long press menu
         View guts = mMediaNotifView.findViewById(R.id.media_guts);
         View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
@@ -155,4 +180,16 @@
             return true; // consumed click
         });
     }
+
+    /**
+     * Sets the listening state of the player.
+     *
+     * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
+     * unnecessary work when the QS panel is closed.
+     *
+     * @param listening True when player should be active. Otherwise, false.
+     */
+    public void setListening(boolean listening) {
+        mSeekBarViewModel.setListening(listening);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 33cc086..c8412ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -103,7 +104,7 @@
     private final NotificationMediaManager mNotificationMediaManager;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final Executor mForegroundExecutor;
-    private final Executor mBackgroundExecutor;
+    private final DelayableExecutor mBackgroundExecutor;
     private LocalMediaManager mLocalMediaManager;
     private MediaDevice mDevice;
     private boolean mUpdateCarousel = false;
@@ -166,7 +167,7 @@
             QSLogger qsLogger,
             NotificationMediaManager notificationMediaManager,
             @Main Executor foregroundExecutor,
-            @Background Executor backgroundExecutor,
+            @Background DelayableExecutor backgroundExecutor,
             @Nullable LocalBluetoothManager localBluetoothManager
     ) {
         super(context, attrs);
@@ -278,7 +279,7 @@
             Log.d(TAG, "creating new player");
             player = new QSMediaPlayer(mContext, this, mNotificationMediaManager,
                     mForegroundExecutor, mBackgroundExecutor);
-
+            player.setListening(mListening);
             if (player.isPlaying()) {
                 mMediaCarousel.addView(player.getView(), 0, lp); // add in front
             } else {
@@ -584,6 +585,9 @@
         if (mListening) {
             refreshAllTiles();
         }
+        for (QSMediaPlayer player : mMediaPlayers) {
+            player.setListening(mListening);
+        }
     }
 
     private String getTilesSpecs() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index be01d75..8fa64d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -43,6 +43,7 @@
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.Utils;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -81,7 +82,7 @@
             QSLogger qsLogger,
             NotificationMediaManager notificationMediaManager,
             @Main Executor foregroundExecutor,
-            @Background Executor backgroundExecutor,
+            @Background DelayableExecutor backgroundExecutor,
             @Nullable LocalBluetoothManager localBluetoothManager
     ) {
         super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, notificationMediaManager,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index df85ed5..66bc177 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -55,6 +55,7 @@
 import android.util.Log;
 import android.view.InputMonitor;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
@@ -416,6 +417,19 @@
             }
         }
 
+        @Override
+        public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
+            if (!verifyCaller("onQuickSwitchToNewTask")) {
+                return;
+            }
+            long token = Binder.clearCallingIdentity();
+            try {
+                mHandler.post(() -> notifyQuickSwitchToNewTask(rotation));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -785,6 +799,12 @@
         }
     }
 
+    private void notifyQuickSwitchToNewTask(@Surface.Rotation int rotation) {
+        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+            mConnectionCallbacks.get(i).onQuickSwitchToNewTask(rotation);
+        }
+    }
+
     public void notifyQuickScrubStarted() {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
             mConnectionCallbacks.get(i).onQuickScrubStarted();
@@ -850,6 +870,7 @@
     public interface OverviewProxyListener {
         default void onConnectionChanged(boolean isConnected) {}
         default void onQuickStepStarted() {}
+        default void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {}
         default void onOverviewShown(boolean fromHome) {}
         default void onQuickScrubStarted() {}
         /** Notify changes in the nav bar button alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index fe2f1f3..1297f99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -177,12 +177,32 @@
                     currentUserId);
             ent.setSensitive(sensitive, deviceSensitive);
             ent.getRow().setNeedsRedaction(needsRedaction);
-            if (mGroupManager.isChildInGroupWithSummary(ent.getSbn())) {
-                NotificationEntry summary = mGroupManager.getGroupSummary(ent.getSbn());
-                List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(summary);
+            boolean isChildInGroup = mGroupManager.isChildInGroupWithSummary(ent.getSbn());
+
+            boolean groupChangesAllowed = mVisualStabilityManager.areGroupChangesAllowed()
+                    || !ent.hasFinishedInitialization();
+            NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn());
+            if (!groupChangesAllowed) {
+                // We don't to change groups while the user is looking at them
+                boolean wasChildInGroup = ent.isChildInGroup();
+                if (isChildInGroup && !wasChildInGroup) {
+                    isChildInGroup = wasChildInGroup;
+                    mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
+                } else if (!isChildInGroup && wasChildInGroup) {
+                    // We allow grouping changes if the group was collapsed
+                    if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
+                        isChildInGroup = wasChildInGroup;
+                        parent = ent.getRow().getNotificationParent().getEntry();
+                        mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
+                    }
+                }
+            }
+
+            if (isChildInGroup) {
+                List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent);
                 if (orderedChildren == null) {
                     orderedChildren = new ArrayList<>();
-                    mTmpChildOrderMap.put(summary, orderedChildren);
+                    mTmpChildOrderMap.put(parent, orderedChildren);
                 }
                 orderedChildren.add(ent);
             } else {
@@ -205,7 +225,7 @@
         }
 
         for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
-            if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getEntry().getSbn())) {
+            if (mEntryManager.getPendingOrActiveNotif(viewToRemove.getEntry().getKey()) != null) {
                 // we are only transferring this notification to its parent, don't generate an
                 // animation
                 mListContainer.setChildTransferInProgress(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 7ef1d0e..1696f07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationContentView
+import com.android.systemui.statusbar.phone.NotificationGroupManager
 import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -60,6 +61,7 @@
 @Singleton
 class ConversationNotificationManager @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
+    private val notificationGroupManager: NotificationGroupManager,
     private val context: Context
 ) {
     // Need this state to be thread safe, since it's accessed from the ui thread
@@ -81,10 +83,19 @@
                             if (rankingMap.getRanking(entry.sbn.key, ranking) &&
                                     ranking.isConversation) {
                                 val important = ranking.channel.isImportantConversation
+                                var changed = false
                                 entry.row?.layouts?.asSequence()
                                         ?.flatMap(::getLayouts)
                                         ?.mapNotNull { it as? ConversationLayout }
-                                        ?.forEach { it.setIsImportantConversation(important) }
+                                        ?.forEach {
+                                            if (important != it.isImportantConversation) {
+                                                it.setIsImportantConversation(important)
+                                                changed = true
+                                            }
+                                        }
+                                if (changed) {
+                                    notificationGroupManager.updateIsolation(entry)
+                                }
                             }
                         }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
index 059d6ff..148cdea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
@@ -95,8 +95,8 @@
 
     private void freeChildContent(NotificationEntry entry) {
         RowContentBindParams params = mStage.getStageParams(entry);
-        params.freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
-        params.freeContentViews(FLAG_CONTENT_VIEW_EXPANDED);
+        params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
+        params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
         mStage.requestRebind(entry, null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index d2f781d..295adae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -252,7 +252,7 @@
     }
 
     @Override
-    public void onReorderingAllowed() {
+    public void onChangeAllowed() {
         updateNotifications("reordering is now allowed");
     }
 
@@ -539,7 +539,8 @@
         }
     }
 
-    private void addNotificationInternal(StatusBarNotification notification,
+    private void addNotificationInternal(
+            StatusBarNotification notification,
             RankingMap rankingMap) throws InflationException {
         String key = notification.getKey();
         if (DEBUG) {
@@ -579,6 +580,9 @@
         for (NotifCollectionListener listener : mNotifCollectionListeners) {
             listener.onEntryAdded(entry);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onRankingApplied();
+        }
     }
 
     public void addNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -635,6 +639,9 @@
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPostEntryUpdated(entry);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onRankingApplied();
+        }
     }
 
     public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -693,6 +700,9 @@
         for (NotifCollectionListener listener : mNotifCollectionListeners) {
             listener.onRankingUpdate(rankingMap);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onRankingApplied();
+        }
     }
 
     private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) {
@@ -799,6 +809,9 @@
      */
     public void updateRanking(RankingMap rankingMap, String reason) {
         updateRankingAndSort(rankingMap, reason);
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onRankingApplied();
+        }
     }
 
     /** Resorts / filters the current notification set with the current RankingMap */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index b357ada..7ac5995 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -42,12 +42,14 @@
 
     private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
 
-    private final ArrayList<Callback> mCallbacks =  new ArrayList<>();
+    private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
+    private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
     private final Handler mHandler;
 
     private boolean mPanelExpanded;
     private boolean mScreenOn;
     private boolean mReorderingAllowed;
+    private boolean mGroupChangedAllowed;
     private boolean mIsTemporaryReorderingAllowed;
     private long mTemporaryReorderingStart;
     private VisibilityLocationProvider mVisibilityLocationProvider;
@@ -83,13 +85,22 @@
 
     /**
      * Add a callback to invoke when reordering is allowed again.
-     * @param callback
      */
     public void addReorderingAllowedCallback(Callback callback) {
-        if (mCallbacks.contains(callback)) {
+        if (mReorderingAllowedCallbacks.contains(callback)) {
             return;
         }
-        mCallbacks.add(callback);
+        mReorderingAllowedCallbacks.add(callback);
+    }
+
+    /**
+     * Add a callback to invoke when group changes are allowed again.
+     */
+    public void addGroupChangesAllowedCallback(Callback callback) {
+        if (mGroupChangesAllowedCallbacks.contains(callback)) {
+            return;
+        }
+        mGroupChangesAllowedCallbacks.add(callback);
     }
 
     /**
@@ -97,7 +108,7 @@
      */
     public void setPanelExpanded(boolean expanded) {
         mPanelExpanded = expanded;
-        updateReorderingAllowed();
+        updateAllowedStates();
     }
 
     /**
@@ -105,7 +116,7 @@
      */
     public void setScreenOn(boolean screenOn) {
         mScreenOn = screenOn;
-        updateReorderingAllowed();
+        updateAllowedStates();
     }
 
     /**
@@ -116,25 +127,30 @@
             return;
         }
         mPulsing = pulsing;
-        updateReorderingAllowed();
+        updateAllowedStates();
     }
 
-    private void updateReorderingAllowed() {
+    private void updateAllowedStates() {
         boolean reorderingAllowed =
                 (!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
         boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
         mReorderingAllowed = reorderingAllowed;
         if (changedToTrue) {
-            notifyCallbacks();
+            notifyChangeAllowed(mReorderingAllowedCallbacks);
+        }
+        boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
+        changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
+        mGroupChangedAllowed = groupChangesAllowed;
+        if (changedToTrue) {
+            notifyChangeAllowed(mGroupChangesAllowedCallbacks);
         }
     }
 
-    private void notifyCallbacks() {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            Callback callback = mCallbacks.get(i);
-            callback.onReorderingAllowed();
+    private void notifyChangeAllowed(ArrayList<Callback> callbacks) {
+        for (int i = 0; i < callbacks.size(); i++) {
+            callbacks.get(i).onChangeAllowed();
         }
-        mCallbacks.clear();
+        callbacks.clear();
     }
 
     /**
@@ -145,6 +161,13 @@
     }
 
     /**
+     * @return whether changes in the grouping should be allowed right now.
+     */
+    public boolean areGroupChangesAllowed() {
+        return mGroupChangedAllowed;
+    }
+
+    /**
      * @return whether a specific notification is allowed to reorder. Certain notifications are
      * allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added
      * notifications or heads-up notifications that are out of view.
@@ -197,12 +220,12 @@
             mTemporaryReorderingStart = SystemClock.elapsedRealtime();
         }
         mIsTemporaryReorderingAllowed = true;
-        updateReorderingAllowed();
+        updateAllowedStates();
     }
 
     private final Runnable mOnTemporaryReorderingExpired = () -> {
         mIsTemporaryReorderingAllowed = false;
-        updateReorderingAllowed();
+        updateAllowedStates();
     };
 
     /**
@@ -229,9 +252,9 @@
 
     public interface Callback {
         /**
-         * Called when reordering is allowed again.
+         * Called when changing is allowed again.
          */
-        void onReorderingAllowed();
+        void onChangeAllowed();
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 0377c090..dd7be27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -67,7 +67,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 
 import java.util.ArrayList;
@@ -137,7 +136,6 @@
      */
     public EditedSuggestionInfo editedSuggestionInfo;
 
-    private NotificationEntry parent; // our parent (if we're in a group)
     private ExpandableNotificationRow row; // the outer expanded view
     private ExpandableNotificationRowController mRowController;
 
@@ -579,10 +577,6 @@
         if (row != null) row.resetUserExpansion();
     }
 
-    public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
-        if (row != null) row.freeContentViewWhenSafe(inflationFlag);
-    }
-
     public boolean rowExists() {
         return row != null;
     }
@@ -715,7 +709,7 @@
     }
 
     public boolean isChildInGroup() {
-        return parent == null;
+        return row != null && row.isChildInGroup();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
new file mode 100644
index 0000000..1bac938
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * A coordinator that elevates important conversation notifications
+ */
+@Singleton
+class ConversationCoordinator @Inject constructor() : Coordinator {
+
+    private val notificationPromoter = object : NotifPromoter(TAG) {
+        override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean {
+            return entry.channel?.isImportantConversation == true
+        }
+    }
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addPromoter(notificationPromoter)
+    }
+
+    companion object {
+        private const val TAG = "ConversationCoordinator"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
index 573c129..2a3b2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
+import static com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager.alertAgain;
 
 import android.annotation.Nullable;
 
@@ -28,6 +29,8 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
@@ -55,6 +58,8 @@
     private static final String TAG = "HeadsUpCoordinator";
 
     private final HeadsUpManager mHeadsUpManager;
+    private final HeadsUpViewBinder mHeadsUpViewBinder;
+    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final NotificationRemoteInputManager mRemoteInputManager;
 
     // tracks the current HeadUpNotification reported by HeadsUpManager
@@ -66,8 +71,12 @@
     @Inject
     public HeadsUpCoordinator(
             HeadsUpManager headsUpManager,
+            HeadsUpViewBinder headsUpViewBinder,
+            NotificationInterruptStateProvider notificationInterruptStateProvider,
             NotificationRemoteInputManager remoteInputManager) {
         mHeadsUpManager = headsUpManager;
+        mHeadsUpViewBinder = headsUpViewBinder;
+        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
         mRemoteInputManager = remoteInputManager;
     }
 
@@ -84,8 +93,51 @@
         return mNotifSection;
     }
 
+    private void onHeadsUpViewBound(NotificationEntry entry) {
+        mHeadsUpManager.showNotification(entry);
+    }
+
     private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
         /**
+         * Notification was just added and if it should heads up, bind the view and then show it.
+         */
+        @Override
+        public void onEntryAdded(NotificationEntry entry) {
+            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
+                mHeadsUpViewBinder.bindHeadsUpView(
+                        entry,
+                        HeadsUpCoordinator.this::onHeadsUpViewBound);
+            }
+        }
+
+        /**
+         * Notification could've updated to be heads up or not heads up. Even if it did update to
+         * heads up, if the notification specified that it only wants to alert once, don't heads
+         * up again.
+         */
+        @Override
+        public void onEntryUpdated(NotificationEntry entry) {
+            boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification());
+            // includes check for whether this notification should be filtered:
+            boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry);
+            final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey());
+            if (wasHeadsUp) {
+                if (shouldHeadsUp) {
+                    mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
+                } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) {
+                    // We don't want this to be interrupting anymore, let's remove it
+                    mHeadsUpManager.removeNotification(
+                            entry.getKey(), false /* removeImmediately */);
+                }
+            } else if (shouldHeadsUp && hunAgain) {
+                // This notification was updated to be heads up, show it!
+                mHeadsUpViewBinder.bindHeadsUpView(
+                        entry,
+                        HeadsUpCoordinator.this::onHeadsUpViewBound);
+            }
+        }
+
+        /**
          * Stop alerting HUNs that are removed from the notification collection
          */
         @Override
@@ -98,6 +150,11 @@
                 mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput);
             }
         }
+
+        @Override
+        public void onEntryCleanUp(NotificationEntry entry) {
+            mHeadsUpViewBinder.abortBindCallback(entry);
+        }
     };
 
     private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() {
@@ -153,6 +210,9 @@
                 mNotifPromoter.invalidateList();
                 mNotifSection.invalidateList();
             }
+            if (!isHeadsUp) {
+                mHeadsUpViewBinder.unbindHeadsUpView(entry);
+            }
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 03c0ae6..2b279bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -56,6 +56,7 @@
             DeviceProvisionedCoordinator deviceProvisionedCoordinator,
             BubbleCoordinator bubbleCoordinator,
             HeadsUpCoordinator headsUpCoordinator,
+            ConversationCoordinator conversationCoordinator,
             PreparationCoordinator preparationCoordinator) {
         dumpManager.registerDumpable(TAG, this);
         mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
@@ -66,6 +67,7 @@
         mCoordinators.add(deviceProvisionedCoordinator);
         mCoordinators.add(bubbleCoordinator);
         if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mCoordinators.add(conversationCoordinator);
             mCoordinators.add(headsUpCoordinator);
             mCoordinators.add(preparationCoordinator);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 742615c..9973ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -33,9 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -63,8 +61,6 @@
     private final NotifViewBarn mViewBarn;
     private final Map<NotificationEntry, Integer> mInflationStates = new ArrayMap<>();
     private final IStatusBarService mStatusBarService;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-    private final HeadsUpManager mHeadsUpManager;
 
     @Inject
     public PreparationCoordinator(
@@ -72,9 +68,7 @@
             NotifInflaterImpl notifInflater,
             NotifInflationErrorManager errorManager,
             NotifViewBarn viewBarn,
-            IStatusBarService service,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
-            HeadsUpManager headsUpManager
+            IStatusBarService service
     ) {
         mLogger = logger;
         mNotifInflater = notifInflater;
@@ -83,8 +77,6 @@
         mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
         mViewBarn = viewBarn;
         mStatusBarService = service;
-        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
-        mHeadsUpManager = headsUpManager;
     }
 
     @Override
@@ -158,11 +150,6 @@
             mLogger.logNotifInflated(entry.getKey());
             mViewBarn.registerViewForEntry(entry, entry.getRow());
             mInflationStates.put(entry, STATE_INFLATED);
-
-            // TODO: should eventually be moved to HeadsUpCoordinator
-            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
-                mHeadsUpManager.showNotification(entry);
-            }
             mNotifInflatingFilter.invalidateList();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 7237284..32f1822 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -227,24 +225,17 @@
 
         final boolean useIncreasedCollapsedHeight =
                 mMessagingUtil.isImportantMessaging(sbn, entry.getImportance());
-        final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
-                && !mPresenter.isPresenterFullyCollapsed();
         final boolean isLowPriority = entry.isAmbient();
 
         RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
         params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-        params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
         params.setUseLowPriority(entry.isAmbient());
 
-        if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
-            params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
-        }
         //TODO: Replace this API with RowContentBindParams directly
         row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
         params.rebindAllContentViews();
         mRowContentBindStage.requestRebind(entry, en -> {
             row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-            row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp);
             row.setIsLowPriority(isLowPriority);
             mInflationCallback.onAsyncInflationFinished(en);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 0c0cded..41ca52d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -74,7 +74,9 @@
      * non-lifetime-extended notification entries will have their ranking object updated.
      *
      * Ranking updates occur whenever a notification is added, updated, or removed, or when a
-     * standalone ranking is sent from the server.
+     * standalone ranking is sent from the server. If a non-standalone ranking is applied, the event
+     * that accompanied the ranking is emitted first (e.g. {@link #onEntryAdded}), followed by the
+     * ranking event.
      */
     default void onRankingApplied() {
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java
new file mode 100644
index 0000000..a7b1f37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java
@@ -0,0 +1,95 @@
+/*
+ * 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.statusbar.notification.headsup;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controller class for old pipeline heads up view binding. It listens to
+ * {@link NotificationEntryManager} entry events and appropriately binds or unbinds the heads up
+ * view.
+ *
+ * This has a subtle contract with {@link NotificationAlertingManager} where this controller handles
+ * the heads up binding, but {@link NotificationAlertingManager} listens for general inflation
+ * events to actually mark it heads up/update. In the new pipeline, we combine the classes.
+ * See {@link HeadsUpCoordinator}.
+ */
+@Singleton
+public class HeadsUpBindController {
+    private final HeadsUpViewBinder mHeadsUpViewBinder;
+    private final NotificationInterruptStateProvider mInterruptStateProvider;
+
+    @Inject
+    HeadsUpBindController(
+            HeadsUpViewBinder headsUpViewBinder,
+            NotificationInterruptStateProvider notificationInterruptStateProvider) {
+        mInterruptStateProvider = notificationInterruptStateProvider;
+        mHeadsUpViewBinder = headsUpViewBinder;
+    }
+
+    /**
+     * Attach this controller and add its listeners.
+     */
+    public void attach(
+            NotificationEntryManager entryManager,
+            HeadsUpManager headsUpManager) {
+        entryManager.addCollectionListener(mCollectionListener);
+        headsUpManager.addListener(mOnHeadsUpChangedListener);
+    }
+
+    private NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
+        @Override
+        public void onEntryAdded(NotificationEntry entry) {
+            if (mInterruptStateProvider.shouldHeadsUp(entry)) {
+                mHeadsUpViewBinder.bindHeadsUpView(entry, null);
+            }
+        }
+
+        @Override
+        public void onEntryUpdated(NotificationEntry entry) {
+            if (mInterruptStateProvider.shouldHeadsUp(entry)) {
+                mHeadsUpViewBinder.bindHeadsUpView(entry, null);
+            }
+        }
+
+        @Override
+        public void onEntryCleanUp(NotificationEntry entry) {
+            mHeadsUpViewBinder.abortBindCallback(entry);
+        }
+    };
+
+    private OnHeadsUpChangedListener mOnHeadsUpChangedListener  = new OnHeadsUpChangedListener() {
+        @Override
+        public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
+            if (!isHeadsUp) {
+                mHeadsUpViewBinder.unbindHeadsUpView(entry);
+            }
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java
new file mode 100644
index 0000000..37acfa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java
@@ -0,0 +1,112 @@
+/*
+ * 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.statusbar.notification.headsup;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
+import android.util.ArrayMap;
+
+import androidx.annotation.Nullable;
+import androidx.core.os.CancellationSignal;
+
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Wrapper around heads up view binding logic. {@link HeadsUpViewBinder} is responsible for
+ * figuring out the right heads up inflation parameters and inflating/freeing the heads up
+ * content view.
+ *
+ * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated
+ * (i.e. when {@link HeadsUpBindController} is removed).
+ */
+@Singleton
+public class HeadsUpViewBinder {
+    private final RowContentBindStage mStage;
+    private final NotificationMessagingUtil mNotificationMessagingUtil;
+    private final Map<NotificationEntry, CancellationSignal> mOngoingBindCallbacks =
+            new ArrayMap<>();
+
+    private NotificationPresenter mNotificationPresenter;
+
+    @Inject
+    HeadsUpViewBinder(
+            NotificationMessagingUtil notificationMessagingUtil,
+            RowContentBindStage bindStage) {
+        mNotificationMessagingUtil = notificationMessagingUtil;
+        mStage = bindStage;
+    }
+
+    /**
+     * Set notification presenter to determine parameters for heads up view inflation.
+     */
+    public void setPresenter(NotificationPresenter presenter) {
+        mNotificationPresenter = presenter;
+    }
+
+    /**
+     * Bind heads up view to the notification row.
+     * @param callback callback after heads up view is bound
+     */
+    public void bindHeadsUpView(NotificationEntry entry, @Nullable BindCallback callback) {
+        RowContentBindParams params = mStage.getStageParams(entry);
+        final boolean isImportantMessage = mNotificationMessagingUtil.isImportantMessaging(
+                entry.getSbn(), entry.getImportance());
+        final boolean useIncreasedHeadsUp = isImportantMessage
+                && !mNotificationPresenter.isPresenterFullyCollapsed();
+        params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
+        CancellationSignal signal = mStage.requestRebind(entry, en -> {
+            en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight());
+            if (callback != null) {
+                callback.onBindFinished(en);
+            }
+        });
+        abortBindCallback(entry);
+        mOngoingBindCallbacks.put(entry, signal);
+    }
+
+    /**
+     * Abort any callbacks waiting for heads up view binding to finish for a given notification.
+     * @param entry notification with bind in progress
+     */
+    public void abortBindCallback(NotificationEntry entry) {
+        CancellationSignal ongoingBindCallback = mOngoingBindCallbacks.remove(entry);
+        if (ongoingBindCallback != null) {
+            ongoingBindCallback.cancel();
+        }
+    }
+
+    /**
+     * Unbind the heads up view from the notification row.
+     */
+    public void unbindHeadsUpView(NotificationEntry entry) {
+        abortBindCallback(entry);
+        mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
+        mStage.requestRebind(entry, null);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 7a7178c..d1cceae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.NotificationListController
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
+import com.android.systemui.statusbar.notification.headsup.HeadsUpBindController
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
@@ -35,6 +36,7 @@
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder
 import com.android.systemui.statusbar.policy.RemoteInputUriController
 import dagger.Lazy
 import java.io.FileDescriptor
@@ -63,7 +65,9 @@
     private val bubbleController: BubbleController,
     private val groupManager: NotificationGroupManager,
     private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
-    private val headsUpManager: HeadsUpManager
+    private val headsUpManager: HeadsUpManager,
+    private val headsUpBindController: HeadsUpBindController,
+    private val headsUpViewBinder: HeadsUpViewBinder
 ) : NotificationsController {
 
     override fun initialize(
@@ -91,6 +95,7 @@
                 presenter,
                 listContainer,
                 bindRowCallback)
+        headsUpViewBinder.setPresenter(presenter)
         notifBindPipelineInitializer.initialize()
 
         if (featureFlags.isNewNotifPipelineEnabled) {
@@ -109,6 +114,7 @@
             groupAlertTransferHelper.bind(entryManager, groupManager)
             headsUpManager.addListener(groupManager)
             headsUpManager.addListener(groupAlertTransferHelper)
+            headsUpBindController.attach(entryManager, headsUpManager)
             groupManager.setHeadsUpManager(headsUpManager)
             groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java
index b572502..5d07098 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.interruption;
 
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import android.app.Notification;
 import android.service.notification.StatusBarNotification;
@@ -95,16 +94,10 @@
         // TODO: Instead of this back and forth, we should listen to changes in heads up and
         // cancel on-going heads up view inflation using the bind pipeline.
         if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) {
-            // Possible for shouldHeadsUp to change between the inflation starting and ending.
-            // If it does and we no longer need to heads up, we should free the view.
-            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
-                mHeadsUpManager.showNotification(entry);
-                if (!mStatusBarStateController.isDozing()) {
-                    // Mark as seen immediately
-                    setNotificationShown(entry.getSbn());
-                }
-            } else {
-                entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP);
+            mHeadsUpManager.showNotification(entry);
+            if (!mStatusBarStateController.isDozing()) {
+                // Mark as seen immediately
+                setNotificationShown(entry.getSbn());
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index be3873a..88cca43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -96,7 +96,7 @@
             return TYPE_NON_PERSON
         }
 
-        val childTypes = groupManager.getLogicalChildren(statusBarNotification)
+        val childTypes = groupManager.getChildren(statusBarNotification)
                 ?.asSequence()
                 ?.map { getPeopleNotificationType(it.sbn, it.ranking) }
                 ?: return TYPE_NON_PERSON
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 255c2ea..2917346 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -17,12 +17,7 @@
 package com.android.systemui.statusbar.notification.row;
 
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
-import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
 import android.animation.Animator;
@@ -463,41 +458,16 @@
      * Marks a content view as freeable, setting it so that future inflations do not reinflate
      * and ensuring that the view is freed when it is safe to remove.
      *
-     * TODO: This should be moved to the respective coordinator and call
-     * {@link RowContentBindParams#freeContentViews} directly after disappear animation
-     * finishes instead of depending on binding API to know when it's "safe".
-     *
      * @param inflationFlag flag corresponding to the content view to be freed
+     * @deprecated By default, {@link NotificationContentInflater#unbindContent} will tell the
+     * view hierarchy to only free when the view is safe to remove so this method is no longer
+     * needed. Will remove when all uses are gone.
      */
+    @Deprecated
     public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
-        // View should not be reinflated in the future
-        Runnable freeViewRunnable = () -> {
-            // Possible for notification to be removed after free request.
-            if (!isRemoved()) {
-                RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
-                params.freeContentViews(inflationFlag);
-                mRowContentBindStage.requestRebind(mEntry, null /* callback */);
-            }
-        };
-        switch (inflationFlag) {
-            case FLAG_CONTENT_VIEW_CONTRACTED:
-                getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED,
-                        freeViewRunnable);
-                break;
-            case FLAG_CONTENT_VIEW_EXPANDED:
-                getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED,
-                        freeViewRunnable);
-                break;
-            case FLAG_CONTENT_VIEW_HEADS_UP:
-                getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
-                        freeViewRunnable);
-                break;
-            case FLAG_CONTENT_VIEW_PUBLIC:
-                getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED,
-                        freeViewRunnable);
-            default:
-                break;
-        }
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.markContentViewsFreeable(inflationFlag);
+        mRowContentBindStage.requestRebind(mEntry, null /* callback */);
     }
 
     /**
@@ -1571,7 +1541,7 @@
                 if (needsRedaction) {
                     params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
                 } else {
-                    params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC);
+                    params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
                 }
                 mRowContentBindStage.requestRebind(mEntry, null /* callback */);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index d744fc3..893e849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.widget.FrameLayout;
@@ -25,12 +28,15 @@
 import androidx.annotation.Nullable;
 import androidx.core.os.CancellationSignal;
 
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -75,14 +81,18 @@
 public final class NotifBindPipeline {
     private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
     private final NotifBindPipelineLogger mLogger;
+    private final List<BindCallback> mScratchCallbacksList = new ArrayList<>();
+    private final Handler mMainHandler;
     private BindStage mStage;
 
     @Inject
     NotifBindPipeline(
             CommonNotifCollection collection,
-            NotifBindPipelineLogger logger) {
+            NotifBindPipelineLogger logger,
+            @Main Looper mainLooper) {
         collection.addCollectionListener(mCollectionListener);
         mLogger = logger;
+        mMainHandler = new NotifBindPipelineHandler(mainLooper);
     }
 
     /**
@@ -107,7 +117,7 @@
         final BindEntry bindEntry = getBindEntry(entry);
         bindEntry.row = row;
         if (bindEntry.invalidated) {
-            startPipeline(entry);
+            requestPipelineRun(entry);
         }
     }
 
@@ -130,7 +140,28 @@
             signal.setOnCancelListener(() -> callbacks.remove(callback));
         }
 
-        startPipeline(entry);
+        requestPipelineRun(entry);
+    }
+
+    /**
+     * Request pipeline to start.
+     *
+     * We avoid starting the pipeline immediately as multiple clients may request rebinds
+     * back-to-back due to a single change (e.g. notification update), and it's better to start
+     * the real work once rather than repeatedly start and cancel it.
+     */
+    private void requestPipelineRun(NotificationEntry entry) {
+        mLogger.logRequestPipelineRun(entry.getKey());
+
+        final BindEntry bindEntry = getBindEntry(entry);
+
+        // Abort any existing pipeline run
+        mStage.abortStage(entry, bindEntry.row);
+
+        if (!mMainHandler.hasMessages(START_PIPELINE_MSG, entry)) {
+            Message msg = Message.obtain(mMainHandler, START_PIPELINE_MSG, entry);
+            mMainHandler.sendMessage(msg);
+        }
     }
 
     /**
@@ -151,7 +182,6 @@
             return;
         }
 
-        mStage.abortStage(entry, row);
         mStage.executeStage(entry, row, (en) -> onPipelineComplete(en));
     }
 
@@ -162,10 +192,15 @@
         mLogger.logFinishedPipeline(entry.getKey(), callbacks.size());
 
         bindEntry.invalidated = false;
-        for (BindCallback cb : callbacks) {
-            cb.onBindFinished(entry);
-        }
+        // Move all callbacks to separate list as callbacks may themselves add/remove callbacks.
+        // TODO: Throw an exception for this re-entrant behavior once we deprecate
+        // NotificationGroupAlertTransferHelper
+        mScratchCallbacksList.addAll(callbacks);
         callbacks.clear();
+        for (int i = 0; i < mScratchCallbacksList.size(); i++) {
+            mScratchCallbacksList.get(i).onBindFinished(entry);
+        }
+        mScratchCallbacksList.clear();
     }
 
     private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
@@ -183,6 +218,7 @@
                 mStage.abortStage(entry, row);
             }
             mStage.deleteStageParams(entry);
+            mMainHandler.removeMessages(START_PIPELINE_MSG, entry);
         }
     };
 
@@ -211,4 +247,25 @@
         public final Set<BindCallback> callbacks = new ArraySet<>();
         public boolean invalidated;
     }
+
+    private static final int START_PIPELINE_MSG = 1;
+
+    private class NotifBindPipelineHandler extends Handler {
+
+        NotifBindPipelineHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case START_PIPELINE_MSG:
+                    NotificationEntry entry = (NotificationEntry) msg.obj;
+                    startPipeline(entry);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown message type: " + msg.what);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index 2717d7a..1997304 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -40,6 +40,14 @@
         })
     }
 
+    fun logRequestPipelineRun(notifKey: String) {
+        buffer.log(TAG, INFO, {
+            str1 = notifKey
+        }, {
+            "Request pipeline run for notif: $str1"
+        })
+    }
+
     fun logStartPipeline(notifKey: String) {
         buffer.log(TAG, INFO, {
             str1 = notifKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 719f74f..9d54437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -118,6 +118,9 @@
             mRemoteViewCache.clearCache(entry);
         }
 
+        // Cancel any pending frees on any view we're trying to bind since we should be bound after.
+        cancelContentViewFrees(row, contentToBind);
+
         AsyncInflationTask task = new AsyncInflationTask(
                 mBgExecutor,
                 mInflateSynchronously,
@@ -198,44 +201,69 @@
     }
 
     /**
-     * Frees the content view associated with the inflation flag.  Will only succeed if the
-     * view is safe to remove.
+     * Frees the content view associated with the inflation flag as soon as the view is not showing.
      *
      * @param inflateFlag the flag corresponding to the content view which should be freed
      */
-    private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
+    private void freeNotificationView(
+            NotificationEntry entry,
+            ExpandableNotificationRow row,
             @InflationFlag int inflateFlag) {
         switch (inflateFlag) {
             case FLAG_CONTENT_VIEW_CONTRACTED:
-                if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
+                row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> {
                     row.getPrivateLayout().setContractedChild(null);
                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED);
-                }
+                });
                 break;
             case FLAG_CONTENT_VIEW_EXPANDED:
-                if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_EXPANDED)) {
+                row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED, () -> {
                     row.getPrivateLayout().setExpandedChild(null);
                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
-                }
+                });
                 break;
             case FLAG_CONTENT_VIEW_HEADS_UP:
-                if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
+                row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, () -> {
                     row.getPrivateLayout().setHeadsUpChild(null);
                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
                     row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
-                }
+                });
                 break;
             case FLAG_CONTENT_VIEW_PUBLIC:
-                if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
+                row.getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> {
                     row.getPublicLayout().setContractedChild(null);
                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
-                }
+                });
                 break;
             default:
                 break;
         }
     }
 
+    /**
+     * Cancel any pending content view frees from {@link #freeNotificationView} for the provided
+     * content views.
+     *
+     * @param row top level notification row containing the content views
+     * @param contentViews content views to cancel pending frees on
+     */
+    private void cancelContentViewFrees(
+            ExpandableNotificationRow row,
+            @InflationFlag int contentViews) {
+        if ((contentViews & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+            row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
+        }
+        if ((contentViews & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+            row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_EXPANDED);
+        }
+        if ((contentViews & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+            row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_HEADSUP);
+        }
+        if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+            row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
+        }
+    }
+
     private static InflationProgress inflateSmartReplyViews(InflationProgress result,
             @InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
             Context packageContext, HeadsUpManager headsUpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 8efdc1b..b18bf01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -385,6 +385,7 @@
      */
     public void setContractedChild(@Nullable View child) {
         if (mContractedChild != null) {
+            mOnContentViewInactiveListeners.remove(mContractedChild);
             mContractedChild.animate().cancel();
             removeView(mContractedChild);
         }
@@ -432,6 +433,7 @@
                     ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
                 }
             }
+            mOnContentViewInactiveListeners.remove(mExpandedChild);
             mExpandedChild.animate().cancel();
             removeView(mExpandedChild);
             mExpandedRemoteInput = null;
@@ -470,6 +472,7 @@
                     ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
                 }
             }
+            mOnContentViewInactiveListeners.remove(mHeadsUpChild);
             mHeadsUpChild.animate().cancel();
             removeView(mHeadsUpChild);
             mHeadsUpRemoteInput = null;
@@ -1108,7 +1111,6 @@
 
     public void onNotificationUpdated(NotificationEntry entry) {
         mStatusBarNotification = entry.getSbn();
-        mOnContentViewInactiveListeners.clear();
         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
         updateAllSingleLineViews();
         ExpandableNotificationRow row = entry.getRow();
@@ -1623,7 +1625,7 @@
      * @param visibleType visible type corresponding to the content view to listen
      * @param listener runnable to run once when the content view becomes inactive
      */
-    public void performWhenContentInactive(int visibleType, Runnable listener) {
+    void performWhenContentInactive(int visibleType, Runnable listener) {
         View view = getViewForVisibleType(visibleType);
         // View is already inactive
         if (view == null || isContentViewInactive(visibleType)) {
@@ -1634,6 +1636,22 @@
     }
 
     /**
+     * Remove content inactive listeners for a given content view . See
+     * {@link #performWhenContentInactive}.
+     *
+     * @param visibleType visible type corresponding to the content type
+     */
+    void removeContentInactiveRunnable(int visibleType) {
+        View view = getViewForVisibleType(visibleType);
+        // View is already inactive
+        if (view == null) {
+            return;
+        }
+
+        mOnContentViewInactiveListeners.remove(view);
+    }
+
+    /**
      * Whether or not the content view is inactive.  This means it should not be visible
      * or the showing content as removing it would cause visual jank.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 8e2bfb8..6fc1264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -20,7 +20,6 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS;
 
 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
 
@@ -44,7 +43,6 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.transition.ChangeBounds;
@@ -56,15 +54,12 @@
 import android.util.Slog;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
-import android.widget.Button;
-import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -517,7 +512,6 @@
         bgHandler.post(
                 new UpdateChannelRunnable(mINotificationManager, mPackageName,
                         mAppUid, mSelectedAction, mNotificationChannel));
-        mVisualStabilityManager.temporarilyAllowReordering();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index 88ed0bb..d3fec69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -109,11 +109,14 @@
     }
 
     /**
-     * Free the content view so that it will no longer be bound after the rebind request.
+     * Mark the content view to be freed. The view may not be immediately freeable since it may
+     * be visible and animating out but this lets the binder know to free the view when safe.
+     * Note that the callback passed into {@link RowContentBindStage#requestRebind}
+     * may return before the view is actually freed since the view is considered up-to-date.
      *
      * @see InflationFlag
      */
-    public void freeContentViews(@InflationFlag int contentViews) {
+    public void markContentViewsFreeable(@InflationFlag int contentViews) {
         mContentViews &= ~contentViews;
         mDirtyContentViews &= ~contentViews;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 0c311b40..5205bab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -130,10 +130,18 @@
         if (mNotificationHeader != null) {
             mNotificationHeader.setAppOpsOnClickListener(listener);
         }
-        mAppOps.setOnClickListener(listener);
-        mCameraIcon.setOnClickListener(listener);
-        mMicIcon.setOnClickListener(listener);
-        mOverlayIcon.setOnClickListener(listener);
+        if (mAppOps != null) {
+            mAppOps.setOnClickListener(listener);
+        }
+        if (mCameraIcon != null) {
+            mCameraIcon.setOnClickListener(listener);
+        }
+        if (mMicIcon != null) {
+            mMicIcon.setOnClickListener(listener);
+        }
+        if (mOverlayIcon != null) {
+            mOverlayIcon.setOnClickListener(listener);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index d38bc9f..d02037c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -20,6 +20,7 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
@@ -555,6 +556,12 @@
         updateSectionBoundaries();
     }
 
+    void setHeaderForegroundColor(@ColorInt int color) {
+        mPeopleHubView.setTextColor(color);
+        mGentleHeader.setForegroundColor(color);
+        mAlertingHeader.setForegroundColor(color);
+    }
+
     /**
      * For now, declare the available notification buckets (sections) here so that other
      * presentation code can decide what to do based on an entry's buckets
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 823b186..6054b50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -33,6 +33,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -3865,9 +3866,16 @@
         initVelocityTrackerIfNotExists();
         mVelocityTracker.addMovement(ev);
 
-        final int action = ev.getAction();
+        final int action = ev.getActionMasked();
+        if (ev.findPointerIndex(mActivePointerId) == -1 && action != MotionEvent.ACTION_DOWN) {
+            // Incomplete gesture, possibly due to window swap mid-gesture. Ignore until a new
+            // one starts.
+            Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent "
+                    + MotionEvent.actionToString(ev.getActionMasked()));
+            return true;
+        }
 
-        switch (action & MotionEvent.ACTION_MASK) {
+        switch (action) {
             case MotionEvent.ACTION_DOWN: {
                 if (getChildCount() == 0 || !isInContentBounds(ev)) {
                     return false;
@@ -4806,7 +4814,9 @@
         mUsingLightTheme = lightTheme;
         Context context = new ContextThemeWrapper(mContext,
                 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI);
-        final int textColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor);
+        final @ColorInt int textColor =
+                Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor);
+        mSectionsManager.setHeaderForegroundColor(textColor);
         mFooterView.setTextColor(textColor);
         mEmptyShadeView.setTextColor(textColor);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
index bc25c71..a1d898f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.notification.stack
 
+import android.annotation.ColorInt
 import android.content.Context
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
+import android.widget.TextView
 import com.android.systemui.R
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
 import com.android.systemui.statusbar.notification.people.DataListener
@@ -31,12 +33,14 @@
         StackScrollerDecorView(context, attrs), SwipeableView {
 
     private lateinit var contents: ViewGroup
+    private lateinit var label: TextView
 
     lateinit var personViewAdapters: Sequence<DataListener<PersonViewModel?>>
         private set
 
     override fun onFinishInflate() {
         contents = requireViewById(R.id.people_list)
+        label = requireViewById(R.id.header_label)
         personViewAdapters = (0 until contents.childCount)
                 .asSequence() // so we can map
                 .mapNotNull { idx ->
@@ -49,6 +53,8 @@
         setVisible(true /* nowVisible */, false /* animate */)
     }
 
+    fun setTextColor(@ColorInt color: Int) = label.setTextColor(color)
+
     override fun findContentView(): View = contents
     override fun findSecondaryView(): View? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index a3d8eec..5777ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -16,9 +16,11 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -124,4 +126,9 @@
         mLabelTextId = resId;
         mLabelView.setText(resId);
     }
+
+    void setForegroundColor(@ColorInt int color) {
+        mLabelView.setTextColor(color);
+        mClearAllButton.setImageTintList(ColorStateList.valueOf(color));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 8efda21..f103bd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -26,10 +27,14 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.ISystemGestureExclusionListener;
 import android.view.InputChannel;
@@ -40,6 +45,7 @@
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -53,8 +59,10 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
@@ -72,6 +80,8 @@
     private static final String TAG = "EdgeBackGestureHandler";
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
             "gestures.back_timeout", 250);
+    private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
+
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
             new ISystemGestureExclusionListener.Stub() {
@@ -88,6 +98,33 @@
                 }
             };
 
+    private OverviewProxyService.OverviewProxyListener mQuickSwitchListener =
+            new OverviewProxyService.OverviewProxyListener() {
+                @Override
+                public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
+                    mStartingQuickstepRotation = rotation;
+                    updateDisabledForQuickstep();
+                }
+            };
+
+    private TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() {
+        @Override
+        public void onRecentTaskListFrozenChanged(boolean frozen) {
+            if (!frozen) {
+                mStartingQuickstepRotation = -1;
+                mDisabledForQuickstep = false;
+            }
+        }
+    };
+
+    private final ContentObserver mFixedRotationObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updatedFixedRotation();
+        }
+    };
+
     private final Context mContext;
     private final OverviewProxyService mOverviewProxyService;
     private PluginManager mPluginManager;
@@ -110,6 +147,11 @@
     private final float mTouchSlop;
     // Duration after which we consider the event as longpress.
     private final int mLongPressTimeout;
+    private int mStartingQuickstepRotation = -1;
+    // We temporarily disable back gesture when user is quickswitching
+    // between apps of different orientations
+    private boolean mDisabledForQuickstep;
+    private boolean mFixedRotationFlagEnabled;
 
     private final PointF mDownPoint = new PointF();
     private final PointF mEndPoint = new PointF();
@@ -193,6 +235,13 @@
      */
     public void onNavBarAttached() {
         mIsAttached = true;
+        updatedFixedRotation();
+        if (mFixedRotationFlagEnabled) {
+            setRotationCallbacks(true);
+        }
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME),
+                false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL);
         updateIsEnabled();
     }
 
@@ -201,9 +250,25 @@
      */
     public void onNavBarDetached() {
         mIsAttached = false;
+        if (mFixedRotationFlagEnabled) {
+            setRotationCallbacks(false);
+        }
+        mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver);
         updateIsEnabled();
     }
 
+    private void setRotationCallbacks(boolean enable) {
+        if (enable) {
+            ActivityManagerWrapper.getInstance().registerTaskStackListener(
+                    mTaskStackChangeListener);
+            mOverviewProxyService.addCallback(mQuickSwitchListener);
+        } else {
+            ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+                    mTaskStackChangeListener);
+            mOverviewProxyService.removeCallback(mQuickSwitchListener);
+        }
+    }
+
     public void onNavigationModeChanged(int mode, Context currentUserContext) {
         mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
         updateIsEnabled();
@@ -405,7 +470,8 @@
             mLogGesture = false;
             mInRejectedExclusion = false;
             mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
-                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
+                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
+                    && !mDisabledForQuickstep;
             if (mAllowGesture) {
                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
                 mEdgeBackPlugin.onMotionEvent(ev);
@@ -466,6 +532,11 @@
         Dependency.get(ProtoTracer.class).update();
     }
 
+    private void updateDisabledForQuickstep() {
+        int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation();
+        mDisabledForQuickstep = mStartingQuickstepRotation != rotation;
+    }
+
     @Override
     public void onDisplayAdded(int displayId) { }
 
@@ -474,6 +545,10 @@
 
     @Override
     public void onDisplayChanged(int displayId) {
+        if (mStartingQuickstepRotation > -1) {
+            updateDisabledForQuickstep();
+        }
+
         if (displayId == mDisplayId) {
             updateDisplaySize();
         }
@@ -502,6 +577,17 @@
         InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
     }
 
+    private void updatedFixedRotation() {
+        boolean oldFlag = mFixedRotationFlagEnabled;
+        mFixedRotationFlagEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0;
+        if (oldFlag == mFixedRotationFlagEnabled) {
+            return;
+        }
+
+        setRotationCallbacks(mFixedRotationFlagEnabled);
+    }
+
     public void setInsets(int leftInset, int rightInset) {
         mLeftInset = leftInset;
         mRightInset = rightInset;
@@ -514,6 +600,7 @@
         pw.println("EdgeBackGestureHandler:");
         pw.println("  mIsEnabled=" + mIsEnabled);
         pw.println("  mAllowGesture=" + mAllowGesture);
+        pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
         pw.println("  mInRejectedExclusion" + mInRejectedExclusion);
         pw.println("  mExcludeRegion=" + mExcludeRegion);
         pw.println("  mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 63fe700..6b0df95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -337,7 +337,7 @@
     //  VisualStabilityManager.Callback overrides:
 
     @Override
-    public void onReorderingAllowed() {
+    public void onChangeAllowed() {
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
             if (isAlerting(entry.getKey())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 8c31a37..dd9c820 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -391,7 +391,9 @@
                         alertNotificationWhenPossible(entry, mHeadsUpManager);
                     } else {
                         // The transfer is no longer valid. Free the content.
-                        entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag());
+                        mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
+                                contentFlag);
+                        mRowContentBindStage.requestRebind(entry, null);
                     }
                 }
             });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index ccf6707..84dd48b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import android.annotation.Nullable;
+import android.app.NotificationChannel;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.Log;
@@ -85,6 +86,17 @@
         return group.expanded;
     }
 
+    /**
+     * @return if the group that this notification is associated with logically is expanded
+     */
+    public boolean isLogicalGroupExpanded(StatusBarNotification sbn) {
+        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        if (group == null) {
+            return false;
+        }
+        return group.expanded;
+    }
+
     public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
         NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group == null) {
@@ -147,7 +159,15 @@
         }
     }
 
+    /**
+     * Notify the group manager that a new entry was added
+     */
     public void onEntryAdded(final NotificationEntry added) {
+        updateIsolation(added);
+        onEntryAddedInternal(added);
+    }
+
+    private void onEntryAddedInternal(final NotificationEntry added) {
         if (added.isRowRemoved()) {
             added.setDebugThrowable(new Throwable());
         }
@@ -193,9 +213,7 @@
     }
 
     private void onEntryBecomingChild(NotificationEntry entry) {
-        if (shouldIsolate(entry)) {
-            isolateNotification(entry);
-        }
+        updateIsolation(entry);
     }
 
     private void updateSuppression(NotificationGroup group) {
@@ -242,15 +260,6 @@
         return count;
     }
 
-    private NotificationEntry getIsolatedChild(String groupKey) {
-        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
-                return mGroupMap.get(sbn.getKey()).summary;
-            }
-        }
-        return null;
-    }
-
     /**
      * Update an entry's group information
      * @param entry notification entry to update
@@ -278,7 +287,7 @@
         if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
             onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
         }
-        onEntryAdded(entry);
+        onEntryAddedInternal(entry);
         mIsUpdatingUnchangedGroup = false;
         if (isIsolated(entry.getSbn().getKey())) {
             mIsolatedEntries.put(entry.getKey(), entry.getSbn());
@@ -413,14 +422,29 @@
             return null;
         }
         ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values());
-        NotificationEntry isolatedChild = getIsolatedChild(summary.getGroupKey());
-        if (isolatedChild != null) {
-            children.add(isolatedChild);
+        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
+            if (sbn.getGroupKey().equals(summary.getGroupKey())) {
+                children.add(mGroupMap.get(sbn.getKey()).summary);
+            }
         }
         return children;
     }
 
     /**
+     * Get the children that are in the summary's group, not including those isolated.
+     *
+     * @param summary summary of a group
+     * @return list of the children
+     */
+    public @Nullable ArrayList<NotificationEntry> getChildren(StatusBarNotification summary) {
+        NotificationGroup group = mGroupMap.get(summary.getGroupKey());
+        if (group == null) {
+            return null;
+        }
+        return new ArrayList<>(group.children.values());
+    }
+
+    /**
      * If there is a {@link NotificationGroup} associated with the provided entry, this method
      * will update the suppression of that group.
      */
@@ -495,17 +519,7 @@
 
     @Override
     public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        onAlertStateChanged(entry, isHeadsUp);
-    }
-
-    private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting) {
-        if (isAlerting) {
-            if (shouldIsolate(entry)) {
-                isolateNotification(entry);
-            }
-        } else {
-            stopIsolatingNotification(entry);
-        }
+        updateIsolation(entry);
     }
 
     /**
@@ -519,13 +533,17 @@
 
     private boolean shouldIsolate(NotificationEntry entry) {
         StatusBarNotification sbn = entry.getSbn();
-        NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
         if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
             return false;
         }
+        NotificationChannel channel = entry.getChannel();
+        if (channel != null && channel.isImportantConversation()) {
+            return true;
+        }
         if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
             return false;
         }
+        NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
         return (sbn.getNotification().fullScreenIntent != null
                     || notificationGroup == null
                     || !notificationGroup.expanded
@@ -545,7 +563,7 @@
 
         mIsolatedEntries.put(sbn.getKey(), sbn);
 
-        onEntryAdded(entry);
+        onEntryAddedInternal(entry);
         // We also need to update the suppression of the old group, because this call comes
         // even before the groupManager knows about the notification at all.
         // When the notification gets added afterwards it is already isolated and therefore
@@ -557,17 +575,31 @@
     }
 
     /**
+     * Update the isolation of an entry, splitting it from the group.
+     */
+    public void updateIsolation(NotificationEntry entry) {
+        boolean isIsolated = isIsolated(entry.getSbn().getKey());
+        if (shouldIsolate(entry)) {
+            if (!isIsolated) {
+                isolateNotification(entry);
+            }
+        } else if (isIsolated) {
+            stopIsolatingNotification(entry);
+        }
+    }
+
+    /**
      * Stop isolating a notification and re-group it with its original logical group.
      *
      * @param entry the notification to un-isolate
      */
     private void stopIsolatingNotification(NotificationEntry entry) {
         StatusBarNotification sbn = entry.getSbn();
-        if (mIsolatedEntries.containsKey(sbn.getKey())) {
+        if (isIsolated(sbn.getKey())) {
             // not isolated anymore, we need to update the groups
             onEntryRemovedInternal(entry, entry.getSbn());
             mIsolatedEntries.remove(sbn.getKey());
-            onEntryAdded(entry);
+            onEntryAddedInternal(entry);
             for (OnGroupChangeListener listener : mListeners) {
                 listener.onGroupsChanged();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 66a1d3f..0d5a149 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -161,7 +161,6 @@
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, false);
         }
-        entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP);
     }
 
     protected void updatePinnedMode() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
index 94aa391..86998ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.policy;
diff --git a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java b/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java
index 711a0df..d731753 100644
--- a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java
@@ -48,6 +48,9 @@
 
         ViewLifecycle(View v) {
             v.addOnAttachStateChangeListener(this);
+            if (v.isAttachedToWindow()) {
+                mLifecycle.markState(RESUMED);
+            }
         }
 
         @NonNull
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index 471149c..6decb88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -23,6 +23,7 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.annotation.UiThreadTest;
@@ -52,7 +53,11 @@
         mDependency.injectMockDependency(NotificationMediaManager.class);
         allowTestableLooperAsMainThread();
         Context context = getContext();
-        mRow = new NotificationTestHelper(context, mDependency).createRow();
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        mRow = helper.createRow();
         mCallback = mock(ExpandHelper.Callback.class);
         mExpandHelper = new ExpandHelper(context, mCallback, 10, 100);
     }
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 6f3fbb9..037f04ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -223,7 +223,10 @@
         mNotificationShadeWindowController.attach();
 
         // Need notifications for bubbles
-        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
+        mNotificationTestHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 7df3983..d2f9127 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -109,7 +109,10 @@
 
     @Before
     public void setUp() throws Exception {
-        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
+        mNotificationTestHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         MockitoAnnotations.initMocks(this);
 
         mEntryA1 = createBubbleEntry(1, "a1", "package.a");
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 a31e3f8d..545de21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -210,7 +210,10 @@
         mNotificationShadeWindowController.attach();
 
         // Need notifications for bubbles
-        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
+        mNotificationTestHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
new file mode 100644
index 0000000..137a126
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.globalactions;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.os.UserManager;
+import android.service.dreams.IDreamManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.management.ControlsListingController;
+import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
[email protected]
+public class GlobalActionsDialogTest extends SysuiTestCase {
+    private GlobalActionsDialog mGlobalActionsDialog;
+
+    @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
+    @Mock private AudioManager mAudioManager;
+    @Mock private IDreamManager mDreamManager;
+    @Mock private DevicePolicyManager mDevicePolicyManager;
+    @Mock private LockPatternUtils mLockPatternUtils;
+    @Mock private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private ContentResolver mContentResolver;
+    @Mock private Resources mResources;
+    @Mock private ConfigurationController mConfigurationController;
+    @Mock private ActivityStarter mActivityStarter;
+    @Mock private KeyguardStateController mKeyguardStateController;
+    @Mock private UserManager mUserManager;
+    @Mock private TrustManager mTrustManager;
+    @Mock private IActivityManager mActivityManager;
+    @Mock private MetricsLogger mMetricsLogger;
+    @Mock private NotificationShadeDepthController mDepthController;
+    @Mock private SysuiColorExtractor mColorExtractor;
+    @Mock private IStatusBarService mStatusBarService;
+    @Mock private BlurUtils mBlurUtils;
+    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
+    @Mock private ControlsUiController mControlsUiController;
+    @Mock private IWindowManager mWindowManager;
+    @Mock private Executor mBackgroundExecutor;
+    @Mock private ControlsListingController mControlsListingController;
+    @Mock private ControlsController mControlsController;
+    @Mock private UiEventLogger mUiEventLogger;
+
+    private TestableLooper mTestableLooper;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTestableLooper = TestableLooper.get(this);
+        allowTestableLooperAsMainThread();
+        mGlobalActionsDialog = new GlobalActionsDialog(mContext,
+                mWindowManagerFuncs,
+                mAudioManager,
+                mDreamManager,
+                mDevicePolicyManager,
+                mLockPatternUtils,
+                mBroadcastDispatcher,
+                mConnectivityManager,
+                mTelephonyManager,
+                mContentResolver,
+                null,
+                mResources,
+                mConfigurationController,
+                mActivityStarter,
+                mKeyguardStateController,
+                mUserManager,
+                mTrustManager,
+                mActivityManager,
+                null,
+                mMetricsLogger,
+                mDepthController,
+                mColorExtractor,
+                mStatusBarService,
+                mBlurUtils,
+                mNotificationShadeWindowController,
+                mControlsUiController,
+                mWindowManager,
+                mBackgroundExecutor,
+                mControlsListingController,
+                mControlsController,
+                mUiEventLogger
+        );
+    }
+    @Test
+    public void testShouldLogVisibility() {
+        mGlobalActionsDialog.onShow(null);
+        verify(mUiEventLogger, times(1))
+                .log(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
new file mode 100644
index 0000000..260f520
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.media
+
+import android.graphics.Color
+import android.content.res.ColorStateList
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.widget.SeekBar
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected]
+public class SeekBarObserverTest : SysuiTestCase() {
+
+    private lateinit var observer: SeekBarObserver
+    @Mock private lateinit var mockView: View
+    private lateinit var seekBarView: SeekBar
+    private lateinit var elapsedTimeView: TextView
+    private lateinit var totalTimeView: TextView
+
+    @Before
+    fun setUp() {
+        mockView = mock(View::class.java)
+        seekBarView = SeekBar(context)
+        elapsedTimeView = TextView(context)
+        totalTimeView = TextView(context)
+        whenever<SeekBar>(
+                mockView.findViewById(R.id.media_progress_bar)).thenReturn(seekBarView)
+        whenever<TextView>(
+                mockView.findViewById(R.id.media_elapsed_time)).thenReturn(elapsedTimeView)
+        whenever<TextView>(mockView.findViewById(R.id.media_total_time)).thenReturn(totalTimeView)
+        observer = SeekBarObserver(mockView)
+    }
+
+    @Test
+    fun seekBarGone() {
+        // WHEN seek bar is disabled
+        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)
+    }
+
+    @Test
+    fun seekBarVisible() {
+        // WHEN seek bar is enabled
+        val isEnabled = true
+        val data = SeekBarViewModel.Progress(isEnabled, true, 3000, 12000, -1)
+        observer.onChanged(data)
+        // THEN seek bar is visible
+        assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
+        assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.VISIBLE)
+        assertThat(totalTimeView.getVisibility()).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun seekBarProgress() {
+        // WHEN seek bar progress is about half
+        val data = SeekBarViewModel.Progress(true, true, 3000, 120000, -1)
+        observer.onChanged(data)
+        // THEN seek bar is visible
+        assertThat(seekBarView.progress).isEqualTo(100)
+        assertThat(seekBarView.max).isEqualTo(120000)
+        assertThat(elapsedTimeView.getText()).isEqualTo("00:03")
+        assertThat(totalTimeView.getText()).isEqualTo("02:00")
+    }
+
+    @Test
+    fun seekBarDisabledWhenSeekNotAvailable() {
+        // WHEN seek is not available
+        val isSeekAvailable = false
+        val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000, -1)
+        observer.onChanged(data)
+        // THEN seek bar is not enabled
+        assertThat(seekBarView.isEnabled()).isFalse()
+    }
+
+    @Test
+    fun seekBarEnabledWhenSeekNotAvailable() {
+        // WHEN seek is available
+        val isSeekAvailable = true
+        val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000, -1)
+        observer.onChanged(data)
+        // THEN seek bar is not enabled
+        assertThat(seekBarView.isEnabled()).isTrue()
+    }
+
+    @Test
+    fun seekBarColor() {
+        // WHEN data included color
+        val data = SeekBarViewModel.Progress(true, true, 3000, 120000, Color.RED)
+        observer.onChanged(data)
+        // THEN seek bar is colored
+        val red = ColorStateList.valueOf(Color.RED)
+        assertThat(elapsedTimeView.getTextColors()).isEqualTo(red)
+        assertThat(totalTimeView.getTextColors()).isEqualTo(red)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
new file mode 100644
index 0000000..f316d04
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -0,0 +1,375 @@
+/*
+ * 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.media
+
+import android.graphics.Color
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.SeekBar
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected]
+public class SeekBarViewModelTest : SysuiTestCase() {
+
+    private lateinit var viewModel: SeekBarViewModel
+    private lateinit var fakeExecutor: FakeExecutor
+    private val taskExecutor: TaskExecutor = object : TaskExecutor() {
+        override fun executeOnDiskIO(runnable: Runnable) {
+            runnable.run()
+        }
+        override fun postToMainThread(runnable: Runnable) {
+            runnable.run()
+        }
+        override fun isMainThread(): Boolean {
+            return true
+        }
+    }
+    @Mock private lateinit var mockController: MediaController
+    @Mock private lateinit var mockTransport: MediaController.TransportControls
+
+    @Before
+    fun setUp() {
+        fakeExecutor = FakeExecutor(FakeSystemClock())
+        viewModel = SeekBarViewModel(fakeExecutor)
+        mockController = mock(MediaController::class.java)
+        mockTransport = mock(MediaController.TransportControls::class.java)
+
+        // LiveData to run synchronously
+        ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
+    }
+
+    @After
+    fun tearDown() {
+        ArchTaskExecutor.getInstance().setDelegate(null)
+    }
+
+    @Test
+    fun updateColor() {
+        viewModel.updateController(mockController, Color.RED)
+        assertThat(viewModel.progress.value!!.color).isEqualTo(Color.RED)
+    }
+
+    @Test
+    fun updateDuration() {
+        // GIVEN that the duration is contained within the metadata
+        val duration = 12000L
+        val metadata = MediaMetadata.Builder().run {
+            putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+            build()
+        }
+        whenever(mockController.getMetadata()).thenReturn(metadata)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN the duration is extracted
+        assertThat(viewModel.progress.value!!.duration).isEqualTo(duration)
+        assertThat(viewModel.progress.value!!.enabled).isTrue()
+    }
+
+    @Test
+    fun updateDurationNegative() {
+        // GIVEN that the duration is negative
+        val duration = -1L
+        val metadata = MediaMetadata.Builder().run {
+            putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+            build()
+        }
+        whenever(mockController.getMetadata()).thenReturn(metadata)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN the seek bar is disabled
+        assertThat(viewModel.progress.value!!.enabled).isFalse()
+    }
+
+    @Test
+    fun updateDurationZero() {
+        // GIVEN that the duration is zero
+        val duration = 0L
+        val metadata = MediaMetadata.Builder().run {
+            putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+            build()
+        }
+        whenever(mockController.getMetadata()).thenReturn(metadata)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN the seek bar is disabled
+        assertThat(viewModel.progress.value!!.enabled).isFalse()
+    }
+
+    @Test
+    fun updateElapsedTime() {
+        // GIVEN that the PlaybackState contins the current position
+        val position = 200L
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_PLAYING, position, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN elapsed time is captured
+        assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(200.toInt())
+    }
+
+    @Test
+    fun updateSeekAvailable() {
+        // GIVEN that seek is included in actions
+        val state = PlaybackState.Builder().run {
+            setActions(PlaybackState.ACTION_SEEK_TO)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN seek is available
+        assertThat(viewModel.progress.value!!.seekAvailable).isTrue()
+    }
+
+    @Test
+    fun updateSeekNotAvailable() {
+        // GIVEN that seek is not included in actions
+        val state = PlaybackState.Builder().run {
+            setActions(PlaybackState.ACTION_PLAY)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN seek is not available
+        assertThat(viewModel.progress.value!!.seekAvailable).isFalse()
+    }
+
+    @Test
+    fun handleSeek() {
+        whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+        viewModel.updateController(mockController, Color.RED)
+        // WHEN user input is dispatched
+        val pos = 42L
+        viewModel.onSeek(pos)
+        fakeExecutor.runAllReady()
+        // THEN transport controls should be used
+        verify(mockTransport).seekTo(pos)
+    }
+
+    @Test
+    fun handleProgressChangedUser() {
+        whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+        viewModel.updateController(mockController, Color.RED)
+        // WHEN user starts dragging the seek bar
+        val pos = 42
+        viewModel.seekBarListener.onProgressChanged(SeekBar(context), pos, true)
+        fakeExecutor.runAllReady()
+        // THEN transport controls should be used
+        verify(mockTransport).seekTo(pos.toLong())
+    }
+
+    @Test
+    fun handleProgressChangedOther() {
+        whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+        viewModel.updateController(mockController, Color.RED)
+        // WHEN user starts dragging the seek bar
+        val pos = 42
+        viewModel.seekBarListener.onProgressChanged(SeekBar(context), pos, false)
+        fakeExecutor.runAllReady()
+        // THEN transport controls should be used
+        verify(mockTransport, never()).seekTo(pos.toLong())
+    }
+
+    @Test
+    fun handleStartTrackingTouch() {
+        whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+        viewModel.updateController(mockController, Color.RED)
+        // WHEN user starts dragging the seek bar
+        val pos = 42
+        val bar = SeekBar(context).apply {
+            progress = pos
+        }
+        viewModel.seekBarListener.onStartTrackingTouch(bar)
+        fakeExecutor.runAllReady()
+        // THEN transport controls should be used
+        verify(mockTransport, never()).seekTo(pos.toLong())
+    }
+
+    @Test
+    fun handleStopTrackingTouch() {
+        whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+        viewModel.updateController(mockController, Color.RED)
+        // WHEN user ends drag
+        val pos = 42
+        val bar = SeekBar(context).apply {
+            progress = pos
+        }
+        viewModel.seekBarListener.onStopTrackingTouch(bar)
+        fakeExecutor.runAllReady()
+        // THEN transport controls should be used
+        verify(mockTransport).seekTo(pos.toLong())
+    }
+
+    @Test
+    fun queuePollTaskWhenPlaying() {
+        // GIVEN that the track is playing
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN a task is queued
+        assertThat(fakeExecutor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun noQueuePollTaskWhenStopped() {
+        // GIVEN that the playback state is stopped
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_STOPPED, 200L, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN an update task is not queued
+        assertThat(fakeExecutor.numPending()).isEqualTo(0)
+    }
+
+    @Test
+    fun queuePollTaskWhenListening() {
+        // GIVEN listening
+        viewModel.listening = true
+        with(fakeExecutor) {
+            advanceClockToNext()
+            runAllReady()
+        }
+        // AND the playback state is playing
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN an update task is queued
+        assertThat(fakeExecutor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun noQueuePollTaskWhenNotListening() {
+        // GIVEN not listening
+        viewModel.listening = false
+        with(fakeExecutor) {
+            advanceClockToNext()
+            runAllReady()
+        }
+        // AND the playback state is playing
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_STOPPED, 200L, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN updated
+        viewModel.updateController(mockController, Color.RED)
+        // THEN an update task is not queued
+        assertThat(fakeExecutor.numPending()).isEqualTo(0)
+    }
+
+    @Test
+    fun pollTaskQueuesAnotherPollTaskWhenPlaying() {
+        // GIVEN that the track is playing
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        viewModel.updateController(mockController, Color.RED)
+        // WHEN the next task runs
+        with(fakeExecutor) {
+            advanceClockToNext()
+            runAllReady()
+        }
+        // THEN another task is queued
+        assertThat(fakeExecutor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun taskUpdatesProgress() {
+        // GIVEN that the PlaybackState contins the current position
+        val position = 200L
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_PLAYING, position, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        viewModel.updateController(mockController, Color.RED)
+        // AND the playback state advances
+        val nextPosition = 300L
+        val nextState = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_PLAYING, nextPosition, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(nextState)
+        // WHEN the task runs
+        with(fakeExecutor) {
+            advanceClockToNext()
+            runAllReady()
+        }
+        // THEN elapsed time is captured
+        assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(nextPosition.toInt())
+    }
+
+    @Test
+    fun startListeningQueuesPollTask() {
+        // GIVEN not listening
+        viewModel.listening = false
+        with(fakeExecutor) {
+            advanceClockToNext()
+            runAllReady()
+        }
+        // AND the playback state is playing
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_STOPPED, 200L, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        viewModel.updateController(mockController, Color.RED)
+        // WHEN start listening
+        viewModel.listening = true
+        // THEN an update task is queued
+        assertThat(fakeExecutor.numPending()).isEqualTo(1)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index dbbbaac..862ebe13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -87,7 +88,7 @@
     @Mock
     private Executor mForegroundExecutor;
     @Mock
-    private Executor mBackgroundExecutor;
+    private DelayableExecutor mBackgroundExecutor;
     @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 83877f2..e55ea41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -98,8 +98,10 @@
                 mLockscreenUserManager);
         mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
+        when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
 
-        mHelper = new NotificationTestHelper(mContext, mDependency);
+        mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
 
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
                 mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 0a38f16..2b94561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -21,6 +21,7 @@
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.widget.FrameLayout;
 
@@ -46,7 +47,10 @@
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
-        mNotificationTestHelper = new NotificationTestHelper(getContext(), mDependency);
+        mNotificationTestHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         mHostLayout = new FrameLayout(getContext());
         mObserver = new AboveShelfObserver(mHostLayout);
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index bf2d598..2904014 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -83,8 +83,8 @@
         mDynamicChildBindController.updateChildContentViews(mGroupNotifs);
 
         // THEN we free content views
-        verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
-        verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_EXPANDED);
+        verify(bindParams).markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
+        verify(bindParams).markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
         verify(mBindStage).requestRebind(eq(lastChild), any());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 97e0a31..277ac24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.annotation.UiThreadTest;
@@ -98,7 +99,11 @@
         mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
         when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
         when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-        mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
+        NotificationTestHelper testHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        mRow = testHelper.createRow();
         mNotificationFilter = new NotificationFilter(mock(StatusBarStateController.class));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index 9079223..3d06c57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -110,7 +110,7 @@
         mVisualStabilityManager.setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
         mVisualStabilityManager.setScreenOn(false);
-        verify(mCallback).onReorderingAllowed();
+        verify(mCallback).onChangeAllowed();
     }
 
     @Test
@@ -119,7 +119,7 @@
         mVisualStabilityManager.setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
         mVisualStabilityManager.setPanelExpanded(false);
-        verify(mCallback).onReorderingAllowed();
+        verify(mCallback).onChangeAllowed();
     }
 
     @Test
@@ -130,7 +130,7 @@
         mVisualStabilityManager.setScreenOn(false);
         mVisualStabilityManager.setScreenOn(true);
         mVisualStabilityManager.setScreenOn(false);
-        verify(mCallback).onReorderingAllowed();
+        verify(mCallback).onChangeAllowed();
     }
 
     @Test
@@ -190,7 +190,7 @@
         mVisualStabilityManager.setPulsing(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
         mVisualStabilityManager.setPulsing(false);
-        verify(mCallback).onReorderingAllowed();
+        verify(mCallback).onChangeAllowed();
     }
 
     @Test
@@ -204,7 +204,7 @@
         mVisualStabilityManager.temporarilyAllowReordering();
 
         // THEN callbacks are notified that reordering is allowed
-        verify(mCallback).onReorderingAllowed();
+        verify(mCallback).onChangeAllowed();
         assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
@@ -218,7 +218,7 @@
         mVisualStabilityManager.temporarilyAllowReordering();
 
         // THEN reordering is still not allowed
-        verify(mCallback, never()).onReorderingAllowed();
+        verify(mCallback, never()).onChangeAllowed();
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
new file mode 100644
index 0000000..dfc627e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import android.app.NotificationChannel
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected]
+class ConversationCoordinatorTest : SysuiTestCase() {
+
+    private var coordinator: ConversationCoordinator = ConversationCoordinator()
+
+    // captured listeners and pluggables:
+    private var promoter: NotifPromoter? = null
+
+    @Mock
+    private val pipeline: NotifPipeline? = null
+    @Mock
+    private val channel: NotificationChannel? = null
+    private var entry: NotificationEntry? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(channel!!.isImportantConversation).thenReturn(true)
+
+        coordinator.attach(pipeline!!)
+
+        // capture arguments:
+        val notifPromoterCaptor = ArgumentCaptor.forClass(NotifPromoter::class.java)
+        verify(pipeline).addPromoter(notifPromoterCaptor.capture())
+        promoter = notifPromoterCaptor.value
+
+        entry = NotificationEntryBuilder().setChannel(channel).build()
+    }
+
+    @Test
+    fun testPromotesCurrentHUN() {
+
+        // only promote important conversations
+        assertTrue(promoter!!.shouldPromoteToTopLevel(entry))
+        assertFalse(promoter!!.shouldPromoteToTopLevel(NotificationEntryBuilder().build()))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
index 0c109c4..f3038ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
@@ -19,6 +19,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -37,6 +39,9 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
@@ -63,6 +68,8 @@
 
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private HeadsUpViewBinder mHeadsUpViewBinder;
+    @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private RemoteInputController mRemoteInputController;
     @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension;
@@ -76,6 +83,8 @@
 
         mCoordinator = new HeadsUpCoordinator(
                 mHeadsUpManager,
+                mHeadsUpViewBinder,
+                mNotificationInterruptStateProvider,
                 mRemoteInputManager
         );
 
@@ -168,6 +177,36 @@
     }
 
     @Test
+    public void testShowHUNOnInflationFinished() {
+        // WHEN a notification should HUN and its inflation is finished
+        when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true);
+
+        ArgumentCaptor<BindCallback> bindCallbackCaptor =
+                ArgumentCaptor.forClass(BindCallback.class);
+        mCollectionListener.onEntryAdded(mEntry);
+        verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture());
+
+        bindCallbackCaptor.getValue().onBindFinished(mEntry);
+
+        // THEN we tell the HeadsUpManager to show the notification
+        verify(mHeadsUpManager).showNotification(mEntry);
+    }
+
+    @Test
+    public void testNoHUNOnInflationFinished() {
+        // WHEN a notification shouldn't HUN and its inflation is finished
+        when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false);
+        ArgumentCaptor<BindCallback> bindCallbackCaptor =
+                ArgumentCaptor.forClass(BindCallback.class);
+        mCollectionListener.onEntryAdded(mEntry);
+
+        // THEN we never bind the heads up view or tell HeadsUpManager to show the notification
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(
+                eq(mEntry), bindCallbackCaptor.capture());
+        verify(mHeadsUpManager, never()).showNotification(mEntry);
+    }
+
+    @Test
     public void testOnEntryRemovedRemovesHeadsUpNotification() {
         // GIVEN the current HUN is mEntry
         setCurrentHUN(mEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 8143cf5..6b9e43b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -20,10 +20,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
-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.os.RemoteException;
 import android.testing.AndroidTestingRunner;
@@ -41,9 +39,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -78,8 +74,6 @@
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private IStatusBarService mService;
     @Mock private NotifInflaterImpl mNotifInflater;
-    @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-    @Mock private HeadsUpManager mHeadsUpManager;
 
     @Before
     public void setUp() {
@@ -94,9 +88,7 @@
                 mNotifInflater,
                 mErrorManager,
                 mock(NotifViewBarn.class),
-                mService,
-                mNotificationInterruptStateProvider,
-                mHeadsUpManager);
+                mService);
 
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
         mCoordinator.attach(mNotifPipeline);
@@ -180,24 +172,4 @@
         // THEN it isn't filtered from shade list
         assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0));
     }
-
-    @Test
-    public void testShowHUNOnInflationFinished() {
-        // WHEN a notification should HUN and its inflation is finished
-        when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true);
-        mCallback.onInflationFinished(mEntry);
-
-        // THEN we tell the HeadsUpManager to show the notification
-        verify(mHeadsUpManager).showNotification(mEntry);
-    }
-
-    @Test
-    public void testNoHUNOnInflationFinished() {
-        // WHEN a notification shouldn't HUN and its inflation is finished
-        when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false);
-        mCallback.onInflationFinished(mEntry);
-
-        // THEN we never tell the HeadsUpManager to show the notification
-        verify(mHeadsUpManager, never()).showNotification(mEntry);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index cb37920..43dcbe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -20,7 +20,6 @@
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,6 +38,7 @@
 import android.app.AppOpsManager;
 import android.app.NotificationChannel;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
@@ -79,7 +79,10 @@
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
-        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
+        mNotificationTestHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         mGroupRow = mNotificationTestHelper.createGroup();
         mGroupRow.setHeadsUpAnimatingAwayListener(
                 animatingAway -> mHeadsUpAnimatingAway = animatingAway);
@@ -135,22 +138,13 @@
     }
 
     @Test
-    public void testFreeContentViewWhenSafe() throws Exception {
-        ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);
-
-        row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP);
-
-        assertNull(row.getPrivateLayout().getHeadsUpChild());
-    }
-
-    @Test
     public void setNeedsRedactionFreesViewWhenFalse() throws Exception {
         ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);
         row.setNeedsRedaction(true);
         row.getPublicLayout().setVisibility(View.GONE);
 
         row.setNeedsRedaction(false);
-
+        TestableLooper.get(this).processAllMessages();
         assertNull(row.getPublicLayout().getContractedChild());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
index 6408f7a..bdd82fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -59,7 +59,10 @@
         MockitoAnnotations.initMocks(this);
         CommonNotifCollection collection = mock(CommonNotifCollection.class);
 
-        mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
+        mBindPipeline = new NotifBindPipeline(
+                collection,
+                mock(NotifBindPipelineLogger.class),
+                TestableLooper.get(this).getLooper());
         mBindPipeline.setStage(mStage);
 
         ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
@@ -78,6 +81,7 @@
         // WHEN content is invalidated
         BindCallback callback = mock(BindCallback.class);
         mStage.requestRebind(mEntry, callback);
+        TestableLooper.get(this).processAllMessages();
 
         // WHEN stage finishes its work
         mStage.doWorkSynchronously();
@@ -94,6 +98,7 @@
         // GIVEN an in-progress pipeline run
         BindCallback callback = mock(BindCallback.class);
         CancellationSignal signal = mStage.requestRebind(mEntry, callback);
+        TestableLooper.get(this).processAllMessages();
 
         // WHEN the callback is cancelled.
         signal.cancel();
@@ -113,10 +118,12 @@
         // WHEN the pipeline is invalidated.
         BindCallback callback = mock(BindCallback.class);
         mStage.requestRebind(mEntry, callback);
+        TestableLooper.get(this).processAllMessages();
 
         // WHEN the pipeline is invalidated again before the work completes.
         BindCallback callback2 = mock(BindCallback.class);
         mStage.requestRebind(mEntry, callback2);
+        TestableLooper.get(this).processAllMessages();
 
         // WHEN the stage finishes all work.
         mStage.doWorkSynchronously();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 481bac2..7c8328d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -86,7 +86,7 @@
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
         mDependency.injectMockDependency(BubbleController.class);
 
-        mHelper = new NotificationTestHelper(mContext, mDependency);
+        mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
 
         mBlockingHelperManager = new NotificationBlockingHelperManager(
                 mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 6a65269..25da741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -40,6 +40,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.view.ViewGroup;
@@ -94,8 +95,11 @@
                 .setContentTitle("Title")
                 .setContentText("Text")
                 .setStyle(new Notification.BigTextStyle().bigText("big text"));
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
-                mBuilder.build());
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow(mBuilder.build());
         mRow = spy(row);
 
         final SmartReplyConstants smartReplyConstants = mock(SmartReplyConstants.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 0f26898..b018b59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -76,13 +76,13 @@
     @Test
     @UiThreadTest
     public void testShowAppOpsIcons() {
-        View mockContracted = mock(View.class);
+        View mockContracted = mock(NotificationHeaderView.class);
         when(mockContracted.findViewById(com.android.internal.R.id.mic))
                 .thenReturn(mockContracted);
-        View mockExpanded = mock(View.class);
+        View mockExpanded = mock(NotificationHeaderView.class);
         when(mockExpanded.findViewById(com.android.internal.R.id.mic))
                 .thenReturn(mockExpanded);
-        View mockHeadsUp = mock(View.class);
+        View mockHeadsUp = mock(NotificationHeaderView.class);
         when(mockHeadsUp.findViewById(com.android.internal.R.id.mic))
                 .thenReturn(mockHeadsUp);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 6998edd..b6bd5e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -816,29 +816,4 @@
         verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
                 anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
     }
-
-    @Test
-    public void testAdjustImportanceTemporarilyAllowsReordering() {
-        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(
-                mShortcutManager,
-                mMockPackageManager,
-                mMockINotificationManager,
-                mVisualStabilityManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mEntry,
-                null,
-                null,
-                mIconFactory,
-                true);
-
-        mNotificationInfo.findViewById(R.id.silence).performClick();
-        mNotificationInfo.findViewById(R.id.done).performClick();
-
-        mTestableLooper.processAllMessages();
-
-        verify(mVisualStabilityManager).temporarilyAllowReordering();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index a5d8a84..be026f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -182,7 +182,8 @@
         NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
         NotifBindPipeline pipeline = new NotifBindPipeline(
                 mEntryManager,
-                mock(NotifBindPipelineLogger.class));
+                mock(NotifBindPipelineLogger.class),
+                TestableLooper.get(this).getLooper());
         mBgExecutor = new FakeExecutor(new FakeSystemClock());
         NotificationContentInflater binder = new NotificationContentInflater(
                 cache,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 462da93..ed46423 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -131,7 +131,7 @@
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
-        mHelper = new NotificationTestHelper(mContext, mDependency);
+        mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 0e67feb..07f2085 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -38,6 +38,7 @@
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
+import android.testing.TestableLooper;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
@@ -92,6 +93,7 @@
     private static final String APP_NAME = "appName";
 
     private final Context mContext;
+    private final TestableLooper mTestLooper;
     private int mId;
     private final NotificationGroupManager mGroupManager;
     private ExpandableNotificationRow mRow;
@@ -103,8 +105,12 @@
     private StatusBarStateController mStatusBarStateController;
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
-    public NotificationTestHelper(Context context, TestableDependency dependency) {
+    public NotificationTestHelper(
+            Context context,
+            TestableDependency dependency,
+            TestableLooper testLooper) {
         mContext = context;
+        mTestLooper = testLooper;
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(BubbleController.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
@@ -133,7 +139,10 @@
 
         CommonNotifCollection collection = mock(CommonNotifCollection.class);
 
-        mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
+        mBindPipeline = new NotifBindPipeline(
+                collection,
+                mock(NotifBindPipelineLogger.class),
+                mTestLooper.getLooper());
         mBindPipeline.setStage(mBindStage);
 
         ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
@@ -414,7 +423,7 @@
                 mPeopleNotificationIdentifier);
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
-        inflateAndWait(entry, mBindStage);
+        inflateAndWait(entry);
 
         // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
         // the callback chain, so we need to make up for not adding it to the group manager
@@ -423,10 +432,10 @@
         return row;
     }
 
-    private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage)
-            throws Exception {
+    private void inflateAndWait(NotificationEntry entry) throws Exception {
         CountDownLatch countDownLatch = new CountDownLatch(1);
-        stage.requestRebind(entry, en -> countDownLatch.countDown());
+        mBindStage.requestRebind(entry, en -> countDownLatch.countDown());
+        mTestLooper.processAllMessages();
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index 0f2482c..96a58e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -93,7 +93,7 @@
 
         // WHEN inflation flags are cleared and stage executed.
         final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
-        params.freeContentViews(flags);
+        params.markContentViewsFreeable(flags);
         mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
 
         // THEN binder unbinds flags.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index b661b28..45f7c5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.row.wrapper;
 
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -43,7 +44,11 @@
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
-        mRow = new NotificationTestHelper(mContext, mDependency).createRow();
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        mRow = helper.createRow();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 18ea774..fbe4d73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -27,6 +27,7 @@
 import android.media.session.PlaybackState;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -97,7 +98,11 @@
         mNotif = builder.build();
         assertTrue(mNotif.hasMediaSession());
 
-        mRow = new NotificationTestHelper(mContext, mDependency).createRow(mNotif);
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        mRow = helper.createRow(mNotif);
 
         RemoteViews views = new RemoteViews(mContext.getPackageName(),
                 com.android.internal.R.layout.notification_template_material_big_media);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index 830e8d9..085bd90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -48,7 +49,11 @@
     public void setup() throws Exception {
         allowTestableLooperAsMainThread();
         mView = mock(View.class);
-        mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        mRow = helper.createRow();
         mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index a2029c7..7037891 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -44,7 +45,10 @@
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
-        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
+        mNotificationTestHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         mGroup = mNotificationTestHelper.createGroup();
         mChildrenContainer = mGroup.getChildrenContainer();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index ba2b946..d795cba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
@@ -66,7 +67,10 @@
                 mBypassController,
                 new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
         allowTestableLooperAsMainThread();
-        NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
+        NotificationTestHelper testHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         mFirst = testHelper.createRow();
         mFirst.setHeadsUpAnimatingAwayListener(animatingAway
                 -> mRoundnessManager.onHeadsupAnimatingAwayChanged(mFirst, animatingAway));
@@ -146,7 +150,10 @@
                 createSection(mFirst, mSecond),
                 createSection(null, null)
         });
-        NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
+        NotificationTestHelper testHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         ExpandableNotificationRow row = testHelper.createRow();
         NotificationEntry entry = mock(NotificationEntry.class);
         when(entry.getRow()).thenReturn(row);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index a74657e..e546dff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.TextView;
@@ -69,7 +70,10 @@
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
-        NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
+        NotificationTestHelper testHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
         mFirst = testHelper.createRow();
         mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher);
         mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index f6a099d..67f94130 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -236,8 +235,7 @@
         verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
         callbackCaptor.getValue().onBindFinished(childEntry);
 
-        verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager
-            .getContentFlag());
+        assertTrue((params.getContentViews() & FLAG_CONTENT_VIEW_HEADS_UP) == 0);
         assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index b9c5b7c..dd28687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -152,7 +152,10 @@
         when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
         when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
 
-        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
+        mNotificationTestHelper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
 
         // Create standard notification with contentIntent
         mNotificationRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 86add98a..e88b514 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -105,8 +105,11 @@
 
     @Test
     public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception {
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
-                .createRow();
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
 
         setTestPendingIntent(view);
@@ -127,7 +130,11 @@
 
     private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)
             throws Exception {
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow(
                 DUMMY_MESSAGE_APP_PKG,
                 UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID),
                 toUser);
@@ -169,8 +176,11 @@
 
     @Test
     public void testNoCrashWithoutVisibilityListener() throws Exception {
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
-                .createRow();
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
 
         view.setOnVisibilityChangedListener(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
index ce8085a..486939d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
@@ -25,6 +25,8 @@
 
 import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -35,12 +37,15 @@
 import android.testing.ViewUtils;
 import android.view.View;
 
+import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleEventObserver;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,39 +54,122 @@
 @SmallTest
 public class SysuiLifecycleTest extends SysuiTestCase {
 
+    private View mView;
+
+    @Before
+    public void setUp() {
+        mView = new View(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        if (mView.isAttachedToWindow()) {
+            ViewUtils.detachView(mView);
+            TestableLooper.get(this).processAllMessages();
+        }
+    }
+
     @Test
     public void testAttach() {
-        View v = new View(mContext);
         LifecycleEventObserver observer = mock(LifecycleEventObserver.class);
-        LifecycleOwner lifecycle = viewAttachLifecycle(v);
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
         lifecycle.getLifecycle().addObserver(observer);
 
-        ViewUtils.attachView(v);
+        ViewUtils.attachView(mView);
         TestableLooper.get(this).processAllMessages();
 
         verify(observer).onStateChanged(eq(lifecycle), eq(ON_CREATE));
         verify(observer).onStateChanged(eq(lifecycle), eq(ON_START));
         verify(observer).onStateChanged(eq(lifecycle), eq(ON_RESUME));
-
-        ViewUtils.detachView(v);
-        TestableLooper.get(this).processAllMessages();
     }
 
     @Test
     public void testDetach() {
-        View v = new View(mContext);
         LifecycleEventObserver observer = mock(LifecycleEventObserver.class);
-        LifecycleOwner lifecycle = viewAttachLifecycle(v);
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
         lifecycle.getLifecycle().addObserver(observer);
 
-        ViewUtils.attachView(v);
+        ViewUtils.attachView(mView);
         TestableLooper.get(this).processAllMessages();
 
-        ViewUtils.detachView(v);
+        ViewUtils.detachView(mView);
         TestableLooper.get(this).processAllMessages();
 
         verify(observer).onStateChanged(eq(lifecycle), eq(ON_PAUSE));
         verify(observer).onStateChanged(eq(lifecycle), eq(ON_STOP));
         verify(observer).onStateChanged(eq(lifecycle), eq(ON_DESTROY));
     }
+
+    @Test
+    public void testStateBeforeAttach() {
+        // WHEN a lifecycle is obtained from a view
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
+        // THEN the lifecycle state should be INITIAZED
+        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(
+                Lifecycle.State.INITIALIZED);
+    }
+
+    @Test
+    public void testStateAfterAttach() {
+        // WHEN a lifecycle is obtained from a view
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
+        // AND the view is attached
+        ViewUtils.attachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        // THEN the lifecycle state should be RESUMED
+        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
+    }
+
+    @Test
+    public void testStateAfterDetach() {
+        // WHEN a lifecycle is obtained from a view
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
+        // AND the view is detached
+        ViewUtils.attachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        ViewUtils.detachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        // THEN the lifecycle state should be DESTROYED
+        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.DESTROYED);
+    }
+
+    @Test
+    public void testStateAfterReattach() {
+        // WHEN a lifecycle is obtained from a view
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
+        // AND the view is re-attached
+        ViewUtils.attachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        ViewUtils.detachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        ViewUtils.attachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        // THEN the lifecycle state should still be DESTROYED, err RESUMED?
+        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
+    }
+
+    @Test
+    public void testStateWhenViewAlreadyAttached() {
+        // GIVEN that a view is already attached
+        ViewUtils.attachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        // WHEN a lifecycle is obtained from a view
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
+        // THEN the lifecycle state should be RESUMED
+        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
+    }
+
+    @Test
+    public void testStateWhenViewAlreadyDetached() {
+        // GIVEN that a view is already detached
+        ViewUtils.attachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        ViewUtils.detachView(mView);
+        TestableLooper.get(this).processAllMessages();
+        // WHEN a lifecycle is obtained from a view
+        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
+        // THEN the lifecycle state should be INITIALIZED
+        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(
+                Lifecycle.State.INITIALIZED);
+    }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4ab035e7..2eaa766 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -6313,7 +6313,8 @@
                 && !nai.networkAgentConfig.allowBypass
                 && nc.getOwnerUid() != Process.SYSTEM_UID
                 && lp.getInterfaceName() != null
-                && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
+                && (lp.hasIPv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute())
+                && (lp.hasIPv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute());
     }
 
     private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 7aaf9be..e1f9a7a 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -137,12 +137,12 @@
 
         @Override
         public File getAdbKeysFile() {
-            return mDebuggingManager.getUserKeyFile();
+            return mDebuggingManager == null ? null : mDebuggingManager.getUserKeyFile();
         }
 
         @Override
         public File getAdbTempKeysFile() {
-            return mDebuggingManager.getAdbTempKeysFile();
+            return mDebuggingManager == null ? null : mDebuggingManager.getAdbTempKeysFile();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 85d28831..62d7eb1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18376,7 +18376,18 @@
                     }
                 }
 
-                proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, finishCallback);
+                Process.enableFreezer(false);
+
+                final RemoteCallback intermediateCallback = new RemoteCallback(
+                        new RemoteCallback.OnResultListener() {
+                        @Override
+                        public void onResult(Bundle result) {
+                            finishCallback.sendResult(result);
+                            Process.enableFreezer(true);
+                        }
+                    }, null);
+
+                proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
                 fd = null;
                 return true;
             }
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 45b9383..4431abe 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics;
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -43,6 +44,7 @@
 import android.os.IBinder;
 import android.os.IHwBinder;
 import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -52,6 +54,8 @@
 import android.os.UserManager;
 import android.util.Slog;
 
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.FrameworkStatsLog;
@@ -93,7 +97,22 @@
     protected final Map<Integer, Long> mAuthenticatorIds =
             Collections.synchronizedMap(new HashMap<>());
     protected final AppOpsManager mAppOps;
-    protected final H mHandler = new H();
+
+    /**
+     * Handler which all subclasses should post events to.
+     */
+    protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(android.os.Message msg) {
+            switch (msg.what) {
+                case MSG_USER_SWITCHING:
+                    handleUserSwitching(msg.arg1);
+                    break;
+                default:
+                    Slog.w(getTag(), "Unknown message:" + msg.what);
+            }
+        }
+    };
 
     private final IBinder mToken = new Binder(); // Used for internal enumeration
     private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>();
@@ -483,23 +502,6 @@
         void resetLockout(byte[] token) throws RemoteException;
     }
 
-    /**
-     * Handler which all subclasses should post events to.
-     */
-    protected final class H extends Handler {
-        @Override
-        public void handleMessage(android.os.Message msg) {
-            switch (msg.what) {
-                case MSG_USER_SWITCHING:
-                    handleUserSwitching(msg.arg1);
-                    break;
-
-                default:
-                    Slog.w(getTag(), "Unknown message:" + msg.what);
-            }
-        }
-    }
-
     private final Runnable mOnTaskStackChangedRunnable = new Runnable() {
         @Override
         public void run() {
@@ -647,8 +649,9 @@
         mContext = context;
         mStatusBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString(
-                com.android.internal.R.string.config_keyguardComponent)).getPackageName();
+        final ComponentName keyguardComponent = ComponentName.unflattenFromString(
+                context.getResources().getString(R.string.config_keyguardComponent));
+        mKeyguardPackage = keyguardComponent != null ? keyguardComponent.getPackageName() : null;
         mAppOps = context.getSystemService(AppOpsManager.class);
         mActivityTaskManager = ((ActivityTaskManager) context.getSystemService(
                 Context.ACTIVITY_TASK_SERVICE)).getService();
@@ -671,8 +674,8 @@
 
         // All client lifecycle must be managed on the handler.
         mHandler.post(() -> {
-            handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
-                    0 /*vendorCode */);
+            Slog.e(getTag(), "Sending BIOMETRIC_ERROR_HW_UNAVAILABLE after HAL crash");
+            handleError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
         });
 
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
@@ -798,9 +801,10 @@
     }
 
     protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) {
-        ClientMonitor client = getCurrentClient();
-
-        client.onEnumerationResult(identifier, remaining);
+        ClientMonitor client = mCurrentClient;
+        if (client != null) {
+            client.onEnumerationResult(identifier, remaining);
+        }
 
         // All templates in the HAL for this user were enumerated
         if (remaining == 0) {
@@ -818,7 +822,7 @@
                 }
                 removeClient(client);
                 startCleanupUnknownHALTemplates();
-            } else {
+            } else if (client != null) {
                 removeClient(client);
             }
         }
@@ -898,12 +902,16 @@
 
     protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
             int callingUid, int callingPid, int callingUserId, boolean fromClient) {
+
+        if (DEBUG) Slog.v(getTag(), "cancelAuthentication(" + opPackageName + ")");
         if (fromClient) {
             // Only check this if cancel was called from the client (app). If cancel was called
             // from BiometricService, it means the dialog was dismissed due to user interaction.
             if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
                     callingUserId)) {
-                if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
+                if (DEBUG) {
+                    Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
+                }
                 return;
             }
         }
@@ -1059,7 +1067,8 @@
      * @param newClient the new client that wants to connect
      * @param initiatedByClient true for authenticate, remove and enroll
      */
-    private void startClient(ClientMonitor newClient, boolean initiatedByClient) {
+    @VisibleForTesting
+    void startClient(ClientMonitor newClient, boolean initiatedByClient) {
         ClientMonitor currentClient = mCurrentClient;
         if (currentClient != null) {
             if (DEBUG) Slog.v(getTag(), "request stop current client " +
@@ -1122,18 +1131,27 @@
             Slog.e(getTag(), "Trying to start null client!");
             return;
         }
+
         if (DEBUG) Slog.v(getTag(), "starting client "
                 + mCurrentClient.getClass().getSuperclass().getSimpleName()
                 + "(" + mCurrentClient.getOwnerString() + ")"
                 + " targetUserId: " + mCurrentClient.getTargetUserId()
                 + " currentUserId: " + mCurrentUserId
                 + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
+
         if (cookie != mCurrentClient.getCookie()) {
             Slog.e(getTag(), "Mismatched cookie");
             return;
         }
-        notifyClientActiveCallbacks(true);
-        mCurrentClient.start();
+
+        int status = mCurrentClient.start();
+        if (status == 0) {
+            notifyClientActiveCallbacks(true);
+        } else {
+            mCurrentClient.onError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                    0 /* vendorCode */);
+            removeClient(mCurrentClient);
+        }
     }
 
     protected void removeClient(ClientMonitor client) {
@@ -1145,7 +1163,7 @@
             }
         }
         if (mCurrentClient != null) {
-            if (DEBUG) Slog.v(getTag(), "Done with client: " + client.getOwnerString());
+            if (DEBUG) Slog.v(getTag(), "Done with client: " + mCurrentClient.getOwnerString());
             mCurrentClient = null;
         }
         if (mPendingClient == null) {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 63a8d7c..696daca 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
+import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -78,6 +79,13 @@
     }
 
     /**
+     * Gets the id of the display to mirror.
+     */
+    public int getDisplayIdToMirrorLocked() {
+        return Display.DEFAULT_DISPLAY;
+    }
+
+    /**
      * Gets the name of the display device.
      *
      * @return The display device name.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index a232051..3afbf66 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -57,6 +57,7 @@
 import android.hardware.display.IDisplayManager;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.display.WifiDisplayStatus;
 import android.hardware.input.InputManagerInternal;
 import android.media.projection.IMediaProjection;
@@ -794,8 +795,8 @@
     }
 
     private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
-            IMediaProjection projection, int callingUid, String packageName, String name, int width,
-            int height, int densityDpi, Surface surface, int flags, String uniqueId) {
+            IMediaProjection projection, int callingUid, String packageName, Surface surface,
+            int flags, VirtualDisplayConfig virtualDisplayConfig) {
         synchronized (mSyncRoot) {
             if (mVirtualDisplayAdapter == null) {
                 Slog.w(TAG, "Rejecting request to create private virtual display "
@@ -804,8 +805,8 @@
             }
 
             DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
-                    callback, projection, callingUid, packageName, name, width, height, densityDpi,
-                    surface, flags, uniqueId);
+                    callback, projection, callingUid, packageName, surface, flags,
+                    virtualDisplayConfig);
             if (device == null) {
                 return -1;
             }
@@ -1480,8 +1481,8 @@
         if (!ownContent) {
             if (display != null && !display.hasContentLocked()) {
                 // If the display does not have any content of its own, then
-                // automatically mirror the default logical display contents.
-                display = null;
+                // automatically mirror the requested logical display contents if possible.
+                display = mLogicalDisplays.get(device.getDisplayIdToMirrorLocked());
             }
             if (display == null) {
                 display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
@@ -1729,6 +1730,28 @@
         }
     }
 
+    @VisibleForTesting
+    int getDisplayIdToMirrorInternal(int displayId) {
+        synchronized (mSyncRoot) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null) {
+                DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+                return displayDevice.getDisplayIdToMirrorLocked();
+            }
+            return Display.INVALID_DISPLAY;
+        }
+    }
+
+    @VisibleForTesting
+    Surface getVirtualDisplaySurfaceInternal(IBinder appToken) {
+        synchronized (mSyncRoot) {
+            if (mVirtualDisplayAdapter == null) {
+                return null;
+            }
+            return mVirtualDisplayAdapter.getVirtualDisplaySurfaceLocked(appToken);
+        }
+    }
+
     private final class DisplayManagerHandler extends Handler {
         public DisplayManagerHandler(Looper looper) {
             super(looper, null, true /*async*/);
@@ -2050,10 +2073,8 @@
         }
 
         @Override // Binder call
-        public int createVirtualDisplay(IVirtualDisplayCallback callback,
-                IMediaProjection projection, String packageName, String name,
-                int width, int height, int densityDpi, Surface surface, int flags,
-                String uniqueId) {
+        public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
+                IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) {
             final int callingUid = Binder.getCallingUid();
             if (!validatePackageName(callingUid, packageName)) {
                 throw new SecurityException("packageName must match the calling uid");
@@ -2061,13 +2082,12 @@
             if (callback == null) {
                 throw new IllegalArgumentException("appToken must not be null");
             }
-            if (TextUtils.isEmpty(name)) {
-                throw new IllegalArgumentException("name must be non-null and non-empty");
+            if (virtualDisplayConfig == null) {
+                throw new IllegalArgumentException("virtualDisplayConfig must not be null");
             }
-            if (width <= 0 || height <= 0 || densityDpi <= 0) {
-                throw new IllegalArgumentException("width, height, and densityDpi must be "
-                        + "greater than 0");
-            }
+            final Surface surface = virtualDisplayConfig.getSurface();
+            int flags = virtualDisplayConfig.getFlags();
+
             if (surface != null && surface.isSingleBuffered()) {
                 throw new IllegalArgumentException("Surface can't be single-buffered");
             }
@@ -2128,7 +2148,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 return createVirtualDisplayInternal(callback, projection, callingUid, packageName,
-                        name, width, height, densityDpi, surface, flags, uniqueId);
+                        surface, flags, virtualDisplayConfig);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index f4f2ead..ccd88483 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -28,6 +28,7 @@
 
 import android.content.Context;
 import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
 import android.os.Handler;
@@ -84,22 +85,24 @@
     }
 
     public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,
-            IMediaProjection projection, int ownerUid, String ownerPackageName, String name,
-            int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) {
+            IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface,
+            int flags, VirtualDisplayConfig virtualDisplayConfig) {
+        String name = virtualDisplayConfig.getName();
         boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
         IBinder appToken = callback.asBinder();
         IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure);
         final String baseUniqueId =
                 UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";
         final int uniqueIndex = getNextUniqueIndex(baseUniqueId);
+        String uniqueId = virtualDisplayConfig.getUniqueId();
         if (uniqueId == null) {
             uniqueId = baseUniqueId + uniqueIndex;
         } else {
             uniqueId = UNIQUE_ID_PREFIX + ownerPackageName + ":" + uniqueId;
         }
         VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
-                ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags,
-                new Callback(callback, mHandler), uniqueId, uniqueIndex);
+                ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler),
+                uniqueId, uniqueIndex, virtualDisplayConfig);
 
         mVirtualDisplayDevices.put(appToken, device);
 
@@ -127,6 +130,14 @@
         }
     }
 
+    @VisibleForTesting
+    Surface getVirtualDisplaySurfaceLocked(IBinder appToken) {
+        VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+        if (device != null) {
+            return device.getSurfaceLocked();
+        }
+        return null;
+    }
 
     public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) {
         VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
@@ -214,20 +225,21 @@
         private int mUniqueIndex;
         private Display.Mode mMode;
         private boolean mIsDisplayOn;
+        private int mDisplayIdToMirror;
 
         public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
-                int ownerUid, String ownerPackageName,
-                String name, int width, int height, int densityDpi, Surface surface, int flags,
-                Callback callback, String uniqueId, int uniqueIndex) {
+                int ownerUid, String ownerPackageName, Surface surface, int flags,
+                Callback callback, String uniqueId, int uniqueIndex,
+                VirtualDisplayConfig virtualDisplayConfig) {
             super(VirtualDisplayAdapter.this, displayToken, uniqueId);
             mAppToken = appToken;
             mOwnerUid = ownerUid;
             mOwnerPackageName = ownerPackageName;
-            mName = name;
-            mWidth = width;
-            mHeight = height;
-            mMode = createMode(width, height, REFRESH_RATE);
-            mDensityDpi = densityDpi;
+            mName = virtualDisplayConfig.getName();
+            mWidth = virtualDisplayConfig.getWidth();
+            mHeight = virtualDisplayConfig.getHeight();
+            mMode = createMode(mWidth, mHeight, REFRESH_RATE);
+            mDensityDpi = virtualDisplayConfig.getDensityDpi();
             mSurface = surface;
             mFlags = flags;
             mCallback = callback;
@@ -235,6 +247,7 @@
             mPendingChanges |= PENDING_SURFACE_CHANGE;
             mUniqueIndex = uniqueIndex;
             mIsDisplayOn = surface != null;
+            mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
         }
 
         @Override
@@ -260,6 +273,16 @@
         }
 
         @Override
+        public int getDisplayIdToMirrorLocked() {
+            return mDisplayIdToMirror;
+        }
+
+        @VisibleForTesting
+        Surface getSurfaceLocked() {
+            return mSurface;
+        }
+
+        @Override
         public boolean hasStableUniqueId() {
             return false;
         }
@@ -332,6 +355,7 @@
             pw.println("mFlags=" + mFlags);
             pw.println("mDisplayState=" + Display.stateToString(mDisplayState));
             pw.println("mStopped=" + mStopped);
+            pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror);
         }
 
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e8d8ed7..ed3b9f1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6227,7 +6227,7 @@
                     cancelNotificationLocked(
                             r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
-                            mSendDelete, childrenFlagChecker);
+                            mSendDelete, childrenFlagChecker, mReason);
                     updateLightsLocked();
                     if (mShortcutHelper != null) {
                         mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
@@ -6687,7 +6687,7 @@
         // notification was a summary and its group key changed.
         if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
             cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
-                    null);
+                    null, REASON_APP_CANCEL);
         }
     }
 
@@ -7892,7 +7892,7 @@
             final int M = canceledNotifications.size();
             for (int i = 0; i < M; i++) {
                 cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
-                        listenerName, false /* sendDelete */, flagChecker);
+                        listenerName, false /* sendDelete */, flagChecker, reason);
             }
             updateLightsLocked();
         }
@@ -7963,7 +7963,7 @@
     // Warning: The caller is responsible for invoking updateLightsLocked().
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
-            String listenerName, boolean sendDelete, FlagChecker flagChecker) {
+            String listenerName, boolean sendDelete, FlagChecker flagChecker, int reason) {
         Notification n = r.getNotification();
         if (!n.isGroupSummary()) {
             return;
@@ -7977,30 +7977,33 @@
         }
 
         cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
-                sendDelete, true, flagChecker);
+                sendDelete, true, flagChecker, reason);
         cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
-                listenerName, sendDelete, false, flagChecker);
+                listenerName, sendDelete, false, flagChecker, reason);
     }
 
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
             NotificationRecord parentNotification, int callingUid, int callingPid,
-            String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) {
+            String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker,
+            int reason) {
         final String pkg = parentNotification.getSbn().getPackageName();
         final int userId = parentNotification.getUserId();
-        final int reason = REASON_GROUP_SUMMARY_CANCELED;
+        final int childReason = REASON_GROUP_SUMMARY_CANCELED;
         for (int i = notificationList.size() - 1; i >= 0; i--) {
             final NotificationRecord childR = notificationList.get(i);
             final StatusBarNotification childSbn = childR.getSbn();
             if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
                     childR.getGroupKey().equals(parentNotification.getGroupKey())
                     && (childR.getFlags() & FLAG_FOREGROUND_SERVICE) == 0
-                    && (flagChecker == null || flagChecker.apply(childR.getFlags()))) {
+                    && (flagChecker == null || flagChecker.apply(childR.getFlags()))
+                    && (!childR.getChannel().isImportantConversation()
+                            || reason != REASON_CANCEL)) {
                 EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
-                        childSbn.getTag(), userId, 0, 0, reason, listenerName);
+                        childSbn.getTag(), userId, 0, 0, childReason, listenerName);
                 notificationList.remove(i);
                 mNotificationsByKey.remove(childR.getKey());
-                cancelNotificationLocked(childR, sendDelete, reason, wasPosted, listenerName);
+                cancelNotificationLocked(childR, sendDelete, childReason, wasPosted, listenerName);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 1b271a7..1d5c304 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -47,6 +47,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutQueryWrapper;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
 import android.content.pm.UserInfo;
@@ -698,13 +699,19 @@
         }
 
         @Override
-        public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
-                String packageName, List shortcutIds, List<LocusId> locusIds,
-                ComponentName componentName, int flags, UserHandle targetUser) {
+        public ParceledListSlice getShortcuts(@NonNull final String callingPackage,
+                @NonNull final ShortcutQueryWrapper query, @NonNull final UserHandle targetUser) {
             ensureShortcutPermission(callingPackage);
             if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) {
                 return new ParceledListSlice<>(Collections.EMPTY_LIST);
             }
+
+            final long changedSince = query.getChangedSince();
+            final String packageName = query.getPackage();
+            final List<String> shortcutIds = query.getShortcutIds();
+            final List<LocusId> locusIds = query.getLocusIds();
+            final ComponentName componentName = query.getActivity();
+            final int flags = query.getQueryFlags();
             if (shortcutIds != null && packageName == null) {
                 throw new IllegalArgumentException(
                         "To query by shortcut ID, package name must also be set");
@@ -723,16 +730,17 @@
         }
 
         @Override
-        public void registerShortcutChangeCallback(String callingPackage, long changedSince,
-                String packageName, List shortcutIds, List<LocusId> locusIds,
-                ComponentName componentName, int flags, IShortcutChangeCallback callback) {
+        public void registerShortcutChangeCallback(@NonNull final String callingPackage,
+                @NonNull final ShortcutQueryWrapper query,
+                @NonNull final IShortcutChangeCallback callback) {
+
             ensureShortcutPermission(callingPackage);
 
-            if (shortcutIds != null && packageName == null) {
+            if (query.getShortcutIds() != null && query.getPackage() == null) {
                 throw new IllegalArgumentException(
                         "To query by shortcut ID, package name must also be set");
             }
-            if (locusIds != null && packageName == null) {
+            if (query.getLocusIds() != null && query.getPackage() == null) {
                 throw new IllegalArgumentException(
                         "To query by locus ID, package name must also be set");
             }
@@ -744,10 +752,7 @@
                 user = null;
             }
 
-            // TODO: When ShortcutQueryWrapper (ag/10323729) is available, pass that directly.
-            ShortcutChangeHandler.QueryInfo query = new ShortcutChangeHandler.QueryInfo(
-                    changedSince, packageName, shortcutIds, locusIds, componentName, flags, user);
-            mShortcutChangeHandler.addShortcutChangeCallback(callback, query);
+            mShortcutChangeHandler.addShortcutChangeCallback(callback, query, user);
         }
 
         @Override
@@ -1081,9 +1086,11 @@
                     new RemoteCallbackList<>();
 
             public synchronized void addShortcutChangeCallback(IShortcutChangeCallback callback,
-                    QueryInfo query) {
+                    ShortcutQueryWrapper query, UserHandle user) {
                 mCallbacks.unregister(callback);
-                mCallbacks.register(callback, query);
+                mCallbacks.register(callback, new QueryInfo(query.getChangedSince(),
+                        query.getPackage(), query.getShortcutIds(), query.getLocusIds(),
+                        query.getActivity(), query.getQueryFlags(), user));
             }
 
             public synchronized void removeShortcutChangeCallback(
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index 7786833..c2ecd41 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -176,8 +176,12 @@
     private void setUpAdbFiles(PersistentData persistentData) {
         AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class);
 
-        writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath());
-        writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());
+        if (adbManager.getAdbKeysFile() != null) {
+            writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath());
+        }
+        if (adbManager.getAdbTempKeysFile() != null) {
+            writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());
+        }
     }
 
     private void configureUser() {
@@ -310,12 +314,6 @@
             AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class);
             File adbKeys = adbManager.getAdbKeysFile();
             File adbTempKeys = adbManager.getAdbTempKeysFile();
-            if (adbKeys == null && adbTempKeys == null) {
-                // This should only be accessible on eng builds that haven't yet set up ADB keys
-                getErrPrintWriter()
-                    .println("No ADB keys stored; not enabling test harness mode");
-                return 1;
-            }
 
             try {
                 byte[] adbKeysBytes = getBytesFromFile(adbKeys);
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index a16dbb7..3f2b5c2 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -11,6 +11,7 @@
 import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
+import android.hardware.display.VirtualDisplayConfig;
 import android.media.ImageReader;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -295,10 +296,12 @@
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
 
+            final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                    DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi);
+            builder.setUniqueId(UNIQUE_DISPLAY_ID);
+            builder.setFlags(flags);
             mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
-                    DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
-                    null /* surface */, flags, null /* callback */, null /* handler */,
-                    UNIQUE_DISPLAY_ID);
+                    builder.build(), null /* callback */, null /* handler */);
 
             if (mVirtualDisplay != null) {
                 updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 10b335e..a446720 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -3553,9 +3553,8 @@
         }
     }
 
-    void reparent(DisplayContent newParent, boolean onTop) {
-        // Real parent of stack is within display object, so we have to delegate re-parenting there.
-        newParent.moveStackToDisplay(this, onTop);
+    void reparent(TaskDisplayArea newParent, boolean onTop) {
+        reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
     }
 
     private void updateSurfaceBounds() {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 2c7ce91..8af8624 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -412,7 +412,7 @@
                 final ActivityStack stack = (ActivityStack) task;
                 stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
                 if (mToDisplay.getDisplayId() != stack.getDisplayId()) {
-                    mToDisplay.moveStackToDisplay(stack, mOnTop);
+                    stack.reparent(mToDisplay.getDefaultTaskDisplayArea(), mOnTop);
                 } else if (mOnTop) {
                     mToDisplay.mTaskContainers.positionStackAtTop(stack,
                             false /* includingParents */);
@@ -566,8 +566,8 @@
     }
 
     void moveRecentsStackToFront(String reason) {
-        final ActivityStack recentsStack = mRootWindowContainer.getDefaultDisplay().getStack(
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
+        final ActivityStack recentsStack = mRootWindowContainer.getDefaultTaskDisplayArea()
+                .getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
         if (recentsStack != null) {
             recentsStack.moveToFront(reason);
         }
@@ -2613,7 +2613,7 @@
                 // from whatever is started from the recents activity, so move the home stack
                 // forward.
                 // TODO (b/115289124): Multi-display supports for recents.
-                mRootWindowContainer.getDefaultDisplay().mTaskContainers.moveHomeStackToFront(
+                mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeStackToFront(
                         "startActivityFromRecents");
             }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7803c73..0b19687 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4283,9 +4283,9 @@
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
-                final Task primary = dc.getRootSplitScreenPrimaryTask();
-                final Task secondary = dc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
+                final TaskDisplayArea tc = mRootWindowContainer.getDefaultTaskDisplayArea();
+                final Task primary = tc.getRootSplitScreenPrimaryTask();
+                final Task secondary = tc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
                         && t.inSplitScreenSecondaryWindowingMode());
                 if (primary == null || secondary == null) {
                     return;
@@ -4301,7 +4301,7 @@
                 if (otherRect == null) {
                     // Temporary estimation... again this is just for tests.
                     otherRect = new Rect(secondary.getBounds());
-                    if (dc.getBounds().width() > dc.getBounds().height()) {
+                    if (tc.getBounds().width() > tc.getBounds().height()) {
                         otherRect.left = primaryRect.right + 6;
                     } else {
                         otherRect.top = primaryRect.bottom + 6;
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 682a142..e8becfa 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -54,7 +54,7 @@
  *      - DisplayArea.Root
  *        - Magnification
  *          - DisplayArea.Tokens (Wallpapers are attached here)
- *          - TaskContainers
+ *          - TaskDisplayArea
  *          - DisplayArea.Tokens (windows above Tasks up to IME are attached here)
  *          - ImeContainers
  *          - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 55b7be779..85517a4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -575,13 +575,6 @@
 
     private RootWindowContainer mRootWindowContainer;
 
-    /**
-     * All of the stacks on this display. Order matters, topmost stack is in front of all other
-     * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls
-     * changing the list should also call {@link #onStackOrderChanged()}.
-     */
-    private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>();
-
     /** Array of all UIDs that are present on the display. */
     private IntArray mDisplayAccessUIDs = new IntArray();
 
@@ -2062,23 +2055,6 @@
         return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
     }
 
-    ActivityStack getRootHomeTask() {
-        return mTaskContainers.getRootHomeTask();
-    }
-
-    /** @return The primary split-screen task, and {@code null} otherwise. */
-    @Nullable ActivityStack getRootSplitScreenPrimaryTask() {
-        return mTaskContainers.getRootSplitScreenPrimaryTask();
-    }
-
-    ActivityStack getRootPinnedTask() {
-        return mTaskContainers.getRootPinnedTask();
-    }
-
-    boolean hasPinnedTask() {
-        return mTaskContainers.getRootPinnedTask() != null;
-    }
-
     /**
      * Returns the topmost stack on the display that is compatible with the input windowing mode and
      * activity type. Null is no compatible stack on the display.
@@ -2095,32 +2071,11 @@
         return mTaskContainers.mChildren.get(index);
     }
 
-    int getIndexOf(ActivityStack stack) {
-        return mTaskContainers.getIndexOf(stack);
-    }
-
-    void removeStack(ActivityStack stack) {
-        mTaskContainers.removeChild(stack);
-    }
-
-    @VisibleForTesting
-    WindowList<ActivityStack> getStacks() {
-        return mTaskContainers.mChildren;
-    }
-
     @VisibleForTesting
     ActivityStack getTopStack() {
         return mTaskContainers.getTopStack();
     }
 
-    ArrayList<Task> getVisibleTasks() {
-        return mTaskContainers.getVisibleTasks();
-    }
-
-    SurfaceControl getSplitScreenDividerAnchor() {
-        return mTaskContainers.getSplitScreenDividerAnchor();
-    }
-
     /**
      * The value is only valid in the scope {@link #onRequestedOverrideConfigurationChanged} of the
      * changing hierarchy and the {@link #onConfigurationChanged} of its children.
@@ -2409,8 +2364,13 @@
         out.set(mDisplayFrames.mStable);
     }
 
-    void moveStackToDisplay(ActivityStack stack, boolean onTop) {
-        stack.reparent(mTaskContainers, onTop ? POSITION_TOP: POSITION_BOTTOM);
+    /**
+     * Get the default display area on the display dedicated to app windows. This one should be used
+     * only as a fallback location for activity launches when no target display area is specified,
+     * or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents).
+     */
+    TaskDisplayArea getDefaultTaskDisplayArea() {
+        return mTaskContainers;
     }
 
     @Override
@@ -2473,7 +2433,7 @@
      */
     Task findTaskForResizePoint(int x, int y) {
         final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
-        return mTmpTaskForResizePointSearchResult.process(mTaskContainers, x, y, delta);
+        return mTmpTaskForResizePointSearchResult.process(getDefaultTaskDisplayArea(), x, y, delta);
     }
 
     void updateTouchExcludeRegion() {
@@ -2512,8 +2472,9 @@
             mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
         }
         amendWindowTapExcludeRegion(mTouchExcludeRegion);
-        // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && mTaskContainers.isSplitScreenModeActivated()) {
+        // TODO(multi-display): Support docked stacks on secondary displays & task containers.
+        if (mDisplayId == DEFAULT_DISPLAY
+                && getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2908,20 +2869,22 @@
         pw.println();
 
         // Dump stack references
-        final ActivityStack homeStack = getRootHomeTask();
+        final ActivityStack homeStack = getDefaultTaskDisplayArea().getRootHomeTask();
         if (homeStack != null) {
             pw.println(prefix + "homeStack=" + homeStack.getName());
         }
-        final ActivityStack pinnedStack = getRootPinnedTask();
+        final ActivityStack pinnedStack = getDefaultTaskDisplayArea().getRootPinnedTask();
         if (pinnedStack != null) {
             pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
         }
-        final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask();
+        final ActivityStack splitScreenPrimaryStack = getDefaultTaskDisplayArea()
+                .getRootSplitScreenPrimaryTask();
         if (splitScreenPrimaryStack != null) {
             pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
         }
-        final ActivityStack recentsStack =
-                getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
+        // TODO: Support recents on non-default task containers
+        final ActivityStack recentsStack = getDefaultTaskDisplayArea().getStack(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
         if (recentsStack != null) {
             pw.println(prefix + "recentsStack=" + recentsStack.getName());
         }
@@ -2955,12 +2918,6 @@
         return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
     }
 
-    /** Returns true if the stack in the windowing mode is visible. */
-    boolean isStackVisible(int windowingMode) {
-        final ActivityStack stack = mTaskContainers.getTopStackInWindowingMode(windowingMode);
-        return stack != null && stack.isVisible();
-    }
-
     /** Find the visible, touch-deliverable window under the given point */
     WindowState getTouchableWinAtPointLocked(float xf, float yf) {
         final int x = (int) xf;
@@ -4367,7 +4324,7 @@
             // We skip IME windows so they're processed just above their target, except
             // in split-screen mode where we process the IME containers above the docked divider.
             return dc.mInputMethodTarget != null
-                    && !dc.mTaskContainers.isSplitScreenModeActivated();
+                    && !dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
         }
 
         /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -5262,7 +5219,7 @@
         // released (no more ActivityStack). But, we cannot release it at that moment or the
         // related WindowContainer will also be removed. So, we set display as removed after
         // reparenting stack finished.
-        final DisplayContent toDisplay = mRootWindowContainer.getDefaultDisplay();
+        final TaskDisplayArea toTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         mRootWindowContainer.mStackSupervisor.beginDeferResume();
         try {
             int numStacks = getStackCount();
@@ -5276,10 +5233,10 @@
                     // If default display is in split-window mode, set windowing mode of the stack
                     // to split-screen secondary. Otherwise, set the windowing mode to undefined by
                     // default to let stack inherited the windowing mode from the new display.
-                    final int windowingMode = toDisplay.mTaskContainers.isSplitScreenModeActivated()
+                    final int windowingMode = toTaskDisplayArea.isSplitScreenModeActivated()
                             ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                             : WINDOWING_MODE_UNDEFINED;
-                    stack.reparent(toDisplay, true /* onTop */);
+                    stack.reparent(toTaskDisplayArea, true /* onTop */);
                     stack.setWindowingMode(windowingMode);
                     lastReparentedStack = stack;
                 }
@@ -5392,34 +5349,6 @@
         mSleeping = asleep;
     }
 
-    /**
-     * Adds a listener to be notified whenever the stack order in the display changes. Currently
-     * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
-     * current animation when the system state changes.
-     */
-    void registerStackOrderChangedListener(OnStackOrderChangedListener listener) {
-        if (!mStackOrderChangedCallbacks.contains(listener)) {
-            mStackOrderChangedCallbacks.add(listener);
-        }
-    }
-
-    /**
-     * Removes a previously registered stack order change listener.
-     */
-    void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) {
-        mStackOrderChangedCallbacks.remove(listener);
-    }
-
-    /**
-     * Notifies of a stack order change
-     * @param stack The stack which triggered the order change
-     */
-    void onStackOrderChanged(ActivityStack stack) {
-        for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
-            mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
-        }
-    }
-
     void setDisplayToSingleTaskInstance() {
         final int childCount = getStackCount();
         if (childCount > 1) {
@@ -5448,22 +5377,6 @@
     }
 
     /**
-     * Callback for when the order of the stacks in the display changes.
-     */
-    interface OnStackOrderChangedListener {
-        void onStackOrderChanged(ActivityStack stack);
-    }
-
-    public void dumpStacks(PrintWriter pw) {
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            pw.print(getStackAt(i).getRootTaskId());
-            if (i > 0) {
-                pw.print(",");
-            }
-        }
-    }
-
-    /**
      * Similar to {@link RootWindowContainer#isAnyNonToastWindowVisibleForUid(int)}, but
      * used for pid.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 221258e..264da9f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2653,8 +2653,8 @@
                     if (mStatusBarController.setBarShowingLw(true)) {
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
                     }
-                } else if (topIsFullscreen
-                        && !mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
+                } else if (topIsFullscreen && !mDisplayContent.getDefaultTaskDisplayArea()
+                        .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
                     if (mStatusBarController.setBarShowingLw(false)) {
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
@@ -3462,10 +3462,10 @@
     }
 
     private Pair<Integer, WindowState> updateSystemBarsLw(WindowState win, int oldVis, int vis) {
-        final boolean dockedStackVisible =
-                mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final boolean freeformStackVisible =
-                mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM);
+        final boolean dockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
+                .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final boolean freeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
+                .isStackVisible(WINDOWING_MODE_FREEFORM);
         final boolean resizing = mDisplayContent.getDockedDividerController().isResizing();
 
         // We need to force system bars when the docked stack is visible, when the freeform stack
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index af89a05..bef80f0 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -594,7 +594,8 @@
 
         // In the presence of the PINNED stack or System Alert windows we unfortunately can not
         // seamlessly rotate.
-        if (mDisplayContent.hasPinnedTask() || mDisplayContent.hasAlertWindowSurfaces()) {
+        if (mDisplayContent.getDefaultTaskDisplayArea().hasPinnedTask()
+                || mDisplayContent.hasAlertWindowSurfaces()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index a9a9df8..a606668 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -153,10 +153,11 @@
         t.reparent(mInputSurface, wc.getSurfaceControl());
     }
 
-    void disposeChannelsLw() {
+    void disposeChannelsLw(SurfaceControl.Transaction t) {
         mService.mInputManager.unregisterInputChannel(mServerChannel);
         mClientChannel.dispose();
         mServerChannel.dispose();
+        t.remove(mInputSurface);
         unlinkFromDeathRecipient();
     }
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 18332b9..8b34b9b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -112,8 +112,9 @@
         @Override
         public void dispose() {
             synchronized (mService.mGlobalLock) {
-                disposeChannelsLw();
+                disposeChannelsLw(mInputMonitor.mInputTransaction);
                 mInputEventReceiver.dispose();
+                mInputMonitor.updateInputWindowsLw(true /* force */);
             }
         }
     }
@@ -195,8 +196,7 @@
 
     private boolean disposeInputConsumer(InputConsumerImpl consumer) {
         if (consumer != null) {
-            consumer.disposeChannelsLw();
-            consumer.hide(mInputTransaction);
+            consumer.disposeChannelsLw(mInputTransaction);
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 007af24..e4e5716 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -295,10 +295,10 @@
     }
 
     private boolean forceShowsSystemBarsForWindowingMode() {
-        final boolean isDockedStackVisible =
-                mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final boolean isFreeformStackVisible =
-                mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM);
+        final boolean isDockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
+                .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final boolean isFreeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
+                .isStackVisible(WINDOWING_MODE_FREEFORM);
         final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing();
 
         // We need to force system bars when the docked stack is visible, when the freeform stack
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index a936e74..57a54d0 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -406,11 +406,12 @@
             // show on top of the lock screen. In this can we want to dismiss the docked
             // stack since it will be complicated/risky to try to put the activity on top
             // of the lock screen in the right fullscreen configuration.
-            final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-            if (!display.mTaskContainers.isSplitScreenModeActivated()) {
+            final TaskDisplayArea taskDisplayArea = mRootWindowContainer
+                    .getDefaultTaskDisplayArea();
+            if (!taskDisplayArea.isSplitScreenModeActivated()) {
                 return;
             }
-            display.mTaskContainers.onSplitScreenModeDismissed();
+            taskDisplayArea.onSplitScreenModeDismissed();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 66dbfd5..02a2741 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -286,7 +286,8 @@
             }
             try {
                 final Rect animatingBounds = new Rect();
-                final ActivityStack pinnedStack = mDisplayContent.getRootPinnedTask();
+                final ActivityStack pinnedStack = mDisplayContent.getDefaultTaskDisplayArea()
+                        .getRootPinnedTask();
                 if (pinnedStack != null) {
                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
                 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 08b0abf..a031fe8 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -52,14 +52,14 @@
  * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
  */
 class RecentsAnimation implements RecentsAnimationCallbacks,
-        DisplayContent.OnStackOrderChangedListener {
+        TaskDisplayArea.OnStackOrderChangedListener {
     private static final String TAG = RecentsAnimation.class.getSimpleName();
 
     private final ActivityTaskManagerService mService;
     private final ActivityStackSupervisor mStackSupervisor;
     private final ActivityStartController mActivityStartController;
     private final WindowManagerService mWindowManager;
-    private final DisplayContent mDefaultDisplay;
+    private final TaskDisplayArea mDefaultTaskDisplayArea;
     private final Intent mTargetIntent;
     private final ComponentName mRecentsComponent;
     private final @Nullable String mRecentsFeatureId;
@@ -84,7 +84,7 @@
             int recentsUid, @Nullable WindowProcessController caller) {
         mService = atm;
         mStackSupervisor = stackSupervisor;
-        mDefaultDisplay = mService.mRootWindowContainer.getDefaultDisplay();
+        mDefaultTaskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
         mActivityStartController = activityStartController;
         mWindowManager = wm;
         mTargetIntent = targetIntent;
@@ -107,7 +107,7 @@
     void preloadRecentsActivity() {
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Preload recents with %s",
                 mTargetIntent);
-        ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
+        ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED,
                 mTargetActivityType);
         ActivityRecord targetActivity = getTargetActivity(targetStack);
         if (targetActivity != null) {
@@ -128,7 +128,8 @@
             // Create the activity record. Because the activity is invisible, this doesn't really
             // start the client.
             startRecentsActivityInBackground("preloadRecents");
-            targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType);
+            targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED,
+                    mTargetActivityType);
             targetActivity = getTargetActivity(targetStack);
             if (targetActivity == null) {
                 Slog.w(TAG, "Cannot start " + mTargetIntent);
@@ -176,12 +177,11 @@
         }
 
         // If the activity is associated with the recents stack, then try and get that first
-        ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
+        ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED,
                 mTargetActivityType);
         ActivityRecord targetActivity = getTargetActivity(targetStack);
         final boolean hasExistingActivity = targetActivity != null;
         if (hasExistingActivity) {
-            final TaskDisplayArea taskDisplayArea = targetActivity.getDisplayArea();
             mRestoreTargetBehindStack = getStackAbove(targetStack);
             if (mRestoreTargetBehindStack == null) {
                 notifyAnimationCancelBeforeStart(recentsAnimationRunner);
@@ -209,7 +209,7 @@
         try {
             if (hasExistingActivity) {
                 // Move the recents activity into place for the animation if it is not top most
-                mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack);
+                mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(targetStack);
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s",
                         targetStack, getStackAbove(targetStack));
 
@@ -225,10 +225,10 @@
                 startRecentsActivityInBackground("startRecentsActivity_noTargetActivity");
 
                 // Move the recents activity into place for the animation
-                targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
+                targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED,
                         mTargetActivityType);
                 targetActivity = getTargetActivity(targetStack);
-                mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack);
+                mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(targetStack);
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s",
                         targetStack, getStackAbove(targetStack));
 
@@ -251,7 +251,7 @@
             mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION,
                     "startRecentsActivity");
             mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
-                    this, mDefaultDisplay.getDisplayId(),
+                    this, mDefaultTaskDisplayArea.getDisplayId(),
                     mStackSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
 
             // If we updated the launch-behind state, update the visibility of the activities after
@@ -262,7 +262,7 @@
                     START_TASK_TO_FRONT, targetActivity);
 
             // Register for stack order changes
-            mDefaultDisplay.registerStackOrderChangedListener(this);
+            mDefaultTaskDisplayArea.registerStackOrderChangedListener(this);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to start recents activity", e);
             throw e;
@@ -280,7 +280,7 @@
                             mWindowManager.getRecentsAnimationController(), reorderMode);
 
             // Unregister for stack order changes
-            mDefaultDisplay.unregisterStackOrderChangedListener(this);
+            mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(this);
 
             final RecentsAnimationController controller =
                     mWindowManager.getRecentsAnimationController();
@@ -308,7 +308,7 @@
                 try {
                     mWindowManager.cleanupRecentsAnimation(reorderMode);
 
-                    final ActivityStack targetStack = mDefaultDisplay.getStack(
+                    final ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(
                             WINDOWING_MODE_UNDEFINED, mTargetActivityType);
                     // Prefer to use the original target activity instead of top activity because
                     // we may have moved another task to top (starting 3p launcher).
@@ -422,7 +422,7 @@
     @Override
     public void onStackOrderChanged(ActivityStack stack) {
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "onStackOrderChanged(): stack=%s", stack);
-        if (mDefaultDisplay.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) {
+        if (mDefaultTaskDisplayArea.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) {
             // The stack is not visible, so ignore this change
             return;
         }
@@ -480,8 +480,8 @@
      * @return The top stack that is not always-on-top.
      */
     private ActivityStack getTopNonAlwaysOnTopStack() {
-        for (int i = mDefaultDisplay.getStackCount() - 1; i >= 0; i--) {
-            final ActivityStack s = mDefaultDisplay.getStackAt(i);
+        for (int i = mDefaultTaskDisplayArea.getStackCount() - 1; i >= 0; i--) {
+            final ActivityStack s = mDefaultTaskDisplayArea.getStackAt(i);
             if (s.getWindowConfiguration().isAlwaysOnTop()) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index a30b70d..84229f0 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -340,9 +340,11 @@
 
         // Make leashes for each of the visible/target tasks and add it to the recents animation to
         // be started
-        final ArrayList<Task> visibleTasks = mDisplayContent.getVisibleTasks();
-        final ActivityStack targetStack = mDisplayContent.getStack(WINDOWING_MODE_UNDEFINED,
-                targetActivityType);
+        // TODO(multi-display-area): Support Recents on multiple task display areas
+        final ArrayList<Task> visibleTasks = mDisplayContent.getDefaultTaskDisplayArea()
+                .getVisibleTasks();
+        final ActivityStack targetStack = mDisplayContent.getDefaultTaskDisplayArea()
+                .getStack(WINDOWING_MODE_UNDEFINED, targetActivityType);
         if (targetStack != null) {
             final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) ->
 	            { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class),
@@ -385,7 +387,8 @@
         }
 
         // Save the minimized home height
-        mMinimizedHomeBounds = mDisplayContent.getRootHomeTask().getBounds();
+        mMinimizedHomeBounds = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask()
+                .getBounds();
 
         mService.mWindowPlacerLocked.performSurfacePlacement();
 
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 770c088..32de699 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -232,10 +232,11 @@
         }
 
         final ActivityTaskManagerService atmService = mTargetStack.mAtmService;
-        DisplayContent display = mTargetStack.getDisplay();
-        final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
+        TaskDisplayArea taskDisplayArea = mTargetStack.getDisplayArea();
+        final boolean singleTaskInstanceDisplay =
+                taskDisplayArea.mDisplayContent.isSingleTaskInstance();
         if (singleTaskInstanceDisplay) {
-            display = atmService.mRootWindowContainer.getDefaultDisplay();
+            taskDisplayArea = atmService.mRootWindowContainer.getDefaultTaskDisplayArea();
         }
 
         final int windowingMode = mTargetStack.getWindowingMode();
@@ -246,7 +247,7 @@
             final boolean alwaysCreateTask = DisplayContent.alwaysCreateStack(windowingMode,
                     activityType);
             final Task task = alwaysCreateTask
-                    ? display.getBottomMostTask() : mTargetStack.getBottomMostTask();
+                    ? taskDisplayArea.getBottomMostTask() : mTargetStack.getBottomMostTask();
             Task targetTask = null;
             if (task != null && r.taskAffinity.equals(task.affinity)) {
                 // If the activity currently at the bottom has the same task affinity as
@@ -257,7 +258,7 @@
             }
             if (targetTask == null) {
                 if (alwaysCreateTask) {
-                    targetTask = display.mTaskContainers.getOrCreateStack(windowingMode,
+                    targetTask = taskDisplayArea.getOrCreateStack(windowingMode,
                             activityType, false /* onTop */);
                 } else {
                     targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2764b12..2eeda4d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1369,11 +1369,11 @@
         }
         calculateDefaultMinimalSizeOfResizeableTasks();
 
-        final DisplayContent defaultDisplay = getDefaultDisplay();
-
-        defaultDisplay.mTaskContainers.getOrCreateStack(WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_HOME, ON_TOP);
-        positionChildAt(POSITION_TOP, defaultDisplay, false /* includingParents */);
+        final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
+        defaultTaskDisplayArea.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
+                ON_TOP);
+        positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
+                false /* includingParents */);
     }
 
     // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display.
@@ -1382,6 +1382,16 @@
     }
 
     /**
+     * Get the default display area on the device dedicated to app windows. This one should be used
+     * only as a fallback location for activity launches when no target display area is specified,
+     * or for cases when multi-instance is not supported yet (like Split-screen, Freeform, PiP or
+     * Recents).
+     */
+    TaskDisplayArea getDefaultTaskDisplayArea() {
+        return mDefaultDisplay.getDefaultTaskDisplayArea();
+    }
+
+    /**
      * Get an existing instance of {@link DisplayContent} that has the given uniqueId. Unique ID is
      * defined in {@link DisplayInfo#uniqueId}.
      *
@@ -1436,12 +1446,8 @@
         return displayContent;
     }
 
-    ActivityRecord getDefaultDisplayHomeActivity() {
-        return getDefaultDisplayHomeActivityForUser(mCurrentUser);
-    }
-
     ActivityRecord getDefaultDisplayHomeActivityForUser(int userId) {
-        return getDisplayContent(DEFAULT_DISPLAY).mTaskContainers.getHomeActivityForUser(userId);
+        return getDefaultTaskDisplayArea().getHomeActivityForUser(userId);
     }
 
     boolean startHomeOnAllDisplays(int userId, String reason) {
@@ -1972,8 +1978,8 @@
         final int focusStackId = topFocusedStack != null
                 ? topFocusedStack.getRootTaskId() : INVALID_TASK_ID;
         // We dismiss the docked stack whenever we switch users.
-        if (getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated()) {
-            getDefaultDisplay().mTaskContainers.onSplitScreenModeDismissed();
+        if (getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
+            getDefaultTaskDisplayArea().onSplitScreenModeDismissed();
         }
         // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
         // also cause all tasks to be moved to the fullscreen stack at a position that is
@@ -1995,7 +2001,7 @@
         final int restoreStackId = mUserStackInFront.get(userId);
         ActivityStack stack = getStack(restoreStackId);
         if (stack == null) {
-            stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask();
+            stack = getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
         }
         final boolean homeInFront = stack.isActivityTypeHome();
         if (stack.isOnHomeDisplay()) {
@@ -2018,7 +2024,7 @@
     void updateUserStack(int userId, ActivityStack stack) {
         if (userId != mCurrentUser) {
             if (stack == null) {
-                stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask();
+                stack = getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
             }
 
             mUserStackInFront.put(userId, stack.getRootTaskId());
@@ -2061,7 +2067,7 @@
             return;
         }
 
-        stack.reparent(displayContent.mDisplayContent, onTop);
+        stack.reparent(displayContent.getDefaultTaskDisplayArea(), onTop);
         // TODO(multi-display): resize stacks properly if moved from split-screen.
     }
 
@@ -2155,8 +2161,8 @@
         // Looking up task on preferred display first
         final DisplayContent preferredDisplay = getDisplayContent(preferredDisplayId);
         if (preferredDisplay != null) {
-            preferredDisplay.mTaskContainers.findTaskLocked(r, true /* isPreferredDisplay */,
-                    mTmpFindTaskResult);
+            preferredDisplay.getDefaultTaskDisplayArea().findTaskLocked(r,
+                    true /* isPreferredDisplay */, mTmpFindTaskResult);
             if (mTmpFindTaskResult.mIdealMatch) {
                 return mTmpFindTaskResult.mRecord;
             }
@@ -2168,7 +2174,7 @@
                 continue;
             }
 
-            display.mTaskContainers.findTaskLocked(r, false /* isPreferredDisplay */,
+            display.getDefaultTaskDisplayArea().findTaskLocked(r, false /* isPreferredDisplay */,
                     mTmpFindTaskResult);
             if (mTmpFindTaskResult.mIdealMatch) {
                 return mTmpFindTaskResult.mRecord;
@@ -2788,8 +2794,10 @@
             }
             final DisplayContent display = getDisplayContentOrCreate(displayId);
             if (display != null) {
-                stack = display.mTaskContainers.getOrCreateStack(r, options, candidateTask,
-                        activityType, onTop);
+                // Falling back to default task container
+                final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+                stack = taskDisplayArea.getOrCreateStack(r, options, candidateTask, activityType,
+                        onTop);
                 if (stack != null) {
                     return stack;
                 }
@@ -2835,7 +2843,7 @@
 
         if (container == null
                 || !canLaunchOnDisplay(r, container.mDisplayContent.mDisplayId)) {
-            container = getDefaultDisplay().mTaskContainers;
+            container = getDefaultTaskDisplayArea();
             if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
                 windowingMode = container.resolveWindowingMode(r, options, candidateTask,
                         activityType);
@@ -2887,7 +2895,7 @@
             // it to the target display.
             if (candidateTask.isRootTask()) {
                 final ActivityStack stack = candidateTask.getStack();
-                displayContent.moveStackToDisplay(stack, true /* onTop */);
+                stack.reparent(displayContent.getDefaultTaskDisplayArea(), true /* onTop */);
                 return stack;
             }
         }
@@ -2918,7 +2926,8 @@
             final int activityType =
                     options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED
                             ? options.getLaunchActivityType() : r.getActivityType();
-            return displayContent.createStack(windowingMode, activityType, true /*onTop*/);
+            final TaskDisplayArea taskDisplayArea = displayContent.getDefaultTaskDisplayArea();
+            return taskDisplayArea.createStack(windowingMode, activityType, true /*onTop*/);
         }
 
         return null;
@@ -2989,7 +2998,8 @@
         if (preferredDisplayArea == null) {
             // Stack is currently detached because it is being removed. Use the previous display it
             // was on.
-            preferredDisplayArea = getDisplayContent(currentFocus.mPrevDisplayId).mTaskContainers;
+            preferredDisplayArea = getDisplayContent(currentFocus.mPrevDisplayId)
+                    .getDefaultTaskDisplayArea();
         }
         final ActivityStack preferredFocusableStack = preferredDisplayArea.getNextFocusableStack(
                 currentFocus, ignoreCurrent);
@@ -3010,7 +3020,7 @@
                 // We've already checked this one
                 continue;
             }
-            final ActivityStack nextFocusableStack = display.mTaskContainers
+            final ActivityStack nextFocusableStack = display.getDefaultTaskDisplayArea()
                     .getNextFocusableStack(currentFocus, ignoreCurrent);
             if (nextFocusableStack != null) {
                 return nextFocusableStack;
@@ -3020,31 +3030,6 @@
         return null;
     }
 
-    /**
-     * Get next valid stack for launching provided activity in the system. This will search across
-     * displays and stacks in last-focused order for a focusable and visible stack, except those
-     * that are on a currently focused display.
-     *
-     * @param r The activity that is being launched.
-     * @param currentFocus The display that previously had focus and thus needs to be ignored when
-     *                     searching for the next candidate.
-     * @return Next valid {@link ActivityStack}, null if not found.
-     */
-    ActivityStack getNextValidLaunchStack(@NonNull ActivityRecord r, int currentFocus) {
-        for (int i = getChildCount() - 1; i >= 0; --i) {
-            final DisplayContent display = getChildAt(i);
-            if (display.mDisplayId == currentFocus) {
-                continue;
-            }
-            final ActivityStack stack = getValidLaunchStackOnDisplay(display.mDisplayId, r,
-                    null /* options */, null /* launchParams */);
-            if (stack != null) {
-                return stack;
-            }
-        }
-        return null;
-    }
-
     boolean handleAppDied(WindowProcessController app) {
         boolean hasVisibleActivities = false;
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 77ef011..13e4d8b 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -127,6 +127,12 @@
      * the new stack becomes resumed after which it will be set to current focused stack.
      */
     ActivityStack mLastFocusedStack;
+    /**
+     * All of the stacks on this display. Order matters, topmost stack is in front of all other
+     * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls
+     * changing the list should also call {@link #onStackOrderChanged()}.
+     */
+    private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>();
 
     TaskDisplayArea(DisplayContent displayContent, WindowManagerService service) {
         super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER);
@@ -336,9 +342,9 @@
             // Since a stack could be repositioned while being one of the child, return
             // current index if that's the same stack we are positioning and it is always on
             // top.
-            final boolean sameStack = mDisplayContent.getStacks().get(i) == stack;
+            final boolean sameStack = mChildren.get(i) == stack;
             if ((sameStack && stack.isAlwaysOnTop())
-                    || (!sameStack && !mDisplayContent.getStacks().get(i).isAlwaysOnTop())) {
+                    || (!sameStack && !mChildren.get(i).isAlwaysOnTop())) {
                 belowAlwaysOnTopPosition = i;
                 break;
             }
@@ -352,7 +358,7 @@
         if (stack.isAlwaysOnTop()) {
             if (hasPinnedTask()) {
                 // Always-on-top stacks go below the pinned stack.
-                maxPosition = mDisplayContent.getStacks().indexOf(mRootPinnedTask) - 1;
+                maxPosition = mChildren.indexOf(mRootPinnedTask) - 1;
             }
             // Always-on-top stacks need to be above all other stacks.
             minPosition = belowAlwaysOnTopPosition
@@ -375,7 +381,7 @@
         targetPosition = Math.min(targetPosition, maxPosition);
         targetPosition = Math.max(targetPosition, minPosition);
 
-        int prevPosition = mDisplayContent.getStacks().indexOf(stack);
+        int prevPosition = mChildren.indexOf(stack);
         // The positions we calculated above (maxPosition, minPosition) do not take into
         // consideration the following edge cases.
         // 1) We need to adjust the position depending on the value "adding".
@@ -471,7 +477,7 @@
 
     @Override
     int getOrientation(int candidate) {
-        if (mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
+        if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
             // Apps and their containers are not allowed to specify an orientation while using
             // root tasks...except for the home stack if it is not resizable and currently
             // visible (top of) its root task.
@@ -637,7 +643,7 @@
             mPreferredTopFocusableStack = null;
         }
         mDisplayContent.releaseSelfIfNeeded();
-        mDisplayContent.onStackOrderChanged(stack);
+        onStackOrderChanged(stack);
     }
 
     void positionStackAt(int position, ActivityStack child, boolean includingParents) {
@@ -716,7 +722,7 @@
             }
         }
 
-        mDisplayContent.onStackOrderChanged(stack);
+        onStackOrderChanged(stack);
     }
 
     ActivityStack getStack(int rootTaskId) {
@@ -765,11 +771,11 @@
                 } else if (stack.getParent() != launchRootTask) {
                     stack.reparent(launchRootTask, position);
                 }
-            } else if (stack.getDisplay() != mDisplayContent || !stack.isRootTask()) {
+            } else if (stack.getDisplayArea() != this || !stack.isRootTask()) {
                 if (stack.getParent() == null) {
                     addStack(stack, position);
                 } else {
-                    stack.reparent(mDisplayContent, onTop);
+                    stack.reparent(this, onTop);
                 }
             }
             // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
@@ -832,7 +838,7 @@
             // Create stack on default display instead since this display can only contain 1 stack.
             // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
             // this goes away once ActivityView is no longer using virtual displays.
-            return mRootWindowContainer.getDefaultDisplay().mTaskContainers.createStack(
+            return mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
                     windowingMode, activityType, onTop, info, intent, createdByOrganizer);
         }
 
@@ -1551,6 +1557,16 @@
         return (index < wc.mChildren.size()) ? (ActivityStack) wc.mChildren.get(index) : null;
     }
 
+    /** Returns true if the stack in the windowing mode is visible. */
+    boolean isStackVisible(int windowingMode) {
+        final ActivityStack stack = getTopStackInWindowingMode(windowingMode);
+        return stack != null && stack.isVisible();
+    }
+
+    void removeStack(ActivityStack stack) {
+        removeChild(stack);
+    }
+
     int getDisplayId() {
         return mDisplayContent.getDisplayId();
     }
@@ -1558,4 +1574,39 @@
     boolean isRemoved() {
         return mDisplayContent.isRemoved();
     }
+
+    /**
+     * Adds a listener to be notified whenever the stack order in the display changes. Currently
+     * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
+     * current animation when the system state changes.
+     */
+    void registerStackOrderChangedListener(OnStackOrderChangedListener listener) {
+        if (!mStackOrderChangedCallbacks.contains(listener)) {
+            mStackOrderChangedCallbacks.add(listener);
+        }
+    }
+
+    /**
+     * Removes a previously registered stack order change listener.
+     */
+    void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) {
+        mStackOrderChangedCallbacks.remove(listener);
+    }
+
+    /**
+     * Notifies of a stack order change
+     * @param stack The stack which triggered the order change
+     */
+    void onStackOrderChanged(ActivityStack stack) {
+        for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
+            mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
+        }
+    }
+
+    /**
+     * Callback for when the order of the stacks in the display changes.
+     */
+    interface OnStackOrderChangedListener {
+        void onStackOrderChanged(ActivityStack stack);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index b641e4c..9ffd8d2 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -277,7 +277,7 @@
                     return null;
                 }
 
-                final Task task = display.mTaskContainers.createStack(windowingMode,
+                final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode,
                         ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
                         true /* createdByOrganizer */);
                 RunningTaskInfo out = task.getTaskInfo();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index e43f4b4..b9b6c08 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -510,7 +510,7 @@
 
     private void findWallpaperTarget() {
         mFindResults.reset();
-        if (mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM)) {
+        if (mDisplayContent.getDefaultTaskDisplayArea().isStackVisible(WINDOWING_MODE_FREEFORM)) {
             // In freeform mode we set the wallpaper as its own target, so we don't need an
             // additional window to make it visible.
             mFindResults.setUseTopWallpaperAsTarget(true);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dfaa0ec..687af64 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7318,8 +7318,9 @@
 
         @Override
         public boolean isStackVisibleLw(int windowingMode) {
-            final DisplayContent dc = getDefaultDisplayContentLocked();
-            return dc.isStackVisible(windowingMode);
+            // TODO(multi-display-area): Support multiple task display areas & displays
+            final TaskDisplayArea tc = mRoot.getDefaultTaskDisplayArea();
+            return tc.isStackVisible(windowingMode);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a332b69..3e2e9be 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -234,7 +234,7 @@
                 if (task.getParent() != newParent) {
                     if (newParent == null) {
                         // Re-parent task to display as a root task.
-                        dc.moveStackToDisplay(as, hop.getToTop());
+                        as.reparent(dc.getDefaultTaskDisplayArea(), hop.getToTop());
                     } else if (newParent.inMultiWindowMode() && !task.isResizeable()
                             && task.isLeafTask()) {
                         Slog.w(TAG, "Can't support task that doesn't support multi-window mode in"
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b87d181..8e7585a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1516,7 +1516,8 @@
         // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
         // associate them with some stack to enable dimming.
         final DisplayContent dc = getDisplayContent();
-        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getRootHomeTask() : null;
+        return mAttrs.type >= FIRST_SYSTEM_WINDOW
+                && dc != null ? dc.getDefaultTaskDisplayArea().getRootHomeTask() : null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index b25383b1..d4470f8 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1066,14 +1066,15 @@
 
 
         if (!w.mSeamlesslyRotated) {
+            // Wallpaper is already updated above when calling setWallpaperPositionAndScale so
+            // we only need to consider the non-wallpaper case here.
             if (!mIsWallpaper) {
                 applyCrop(clipRect, recoveringMemory);
-                mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
+                mSurfaceController.setMatrixInTransaction(
+                        mDsDx * w.mHScale * mExtraHScale,
                         mDtDx * w.mVScale * mExtraVScale,
                         mDtDy * w.mHScale * mExtraHScale,
                         mDsDy * w.mVScale * mExtraVScale, recoveringMemory);
-            } else {
-                setWallpaperPositionAndScale(mXOffset, mYOffset, mWallpaperScale, recoveringMemory);
             }
         }
 
@@ -1152,13 +1153,20 @@
                             mSurfaceController, mShownAlpha, mDsDx, w.mHScale, mDtDx, w.mVScale,
                             mDtDy, w.mHScale, mDsDy, w.mVScale, w);
 
-            boolean prepared =
-                mSurfaceController.prepareToShowInTransaction(mShownAlpha,
+            boolean prepared = true;
+
+            if (mIsWallpaper) {
+                setWallpaperPositionAndScale(
+                        mXOffset, mYOffset, mWallpaperScale, recoveringMemory);
+            } else {
+                prepared =
+                    mSurfaceController.prepareToShowInTransaction(mShownAlpha,
                         mDsDx * w.mHScale * mExtraHScale,
                         mDtDx * w.mVScale * mExtraVScale,
                         mDtDy * w.mHScale * mExtraHScale,
                         mDsDy * w.mVScale * mExtraVScale,
                         recoveringMemory);
+            }
 
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index d7c97b9..f7ea953 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -127,6 +127,7 @@
             mBLASTSurfaceControl = win.makeSurface()
                 .setParent(mSurfaceControl)
                 .setName("BLAST Adapter Layer")
+                .setHidden(false)
                 .setBLASTLayer()
                 .build();
         }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 3c2b6ec..7457a1d 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -397,7 +397,8 @@
     void assignLayer(SurfaceControl.Transaction t, int layer) {
         if (windowType == TYPE_DOCK_DIVIDER) {
             // See {@link DisplayContent#mSplitScreenDividerAnchor}
-            super.assignRelativeLayer(t, mDisplayContent.getSplitScreenDividerAnchor(), 1);
+            super.assignRelativeLayer(t,
+                    mDisplayContent.getDefaultTaskDisplayArea().getSplitScreenDividerAnchor(), 1);
         } else if (mRoundedCornerOverlay) {
             super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
         } else {
diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp
index b13d330..02bb0bc 100644
--- a/services/incremental/Android.bp
+++ b/services/incremental/Android.bp
@@ -50,6 +50,7 @@
         "libbinder",
         "libcrypto",
         "libcutils",
+        "libdataloader",
         "libincfs",
         "liblog",
         "libz",
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 5e3c337d..0da1673 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "IncrementalService"
 
 #include "IncrementalService.h"
+#include "IncrementalServiceValidation.h"
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -35,6 +36,7 @@
 #include <uuid/uuid.h>
 #include <zlib.h>
 
+#include <charconv>
 #include <ctime>
 #include <filesystem>
 #include <iterator>
@@ -49,6 +51,9 @@
 using namespace android::content::pm;
 namespace fs = std::filesystem;
 
+constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS";
+constexpr const char* kOpUsage = "android:get_usage_stats";
+
 namespace android::incremental {
 
 namespace {
@@ -231,6 +236,7 @@
       : mVold(sm.getVoldService()),
         mDataLoaderManager(sm.getDataLoaderManager()),
         mIncFs(sm.getIncFs()),
+        mAppOpsManager(sm.getAppOpsManager()),
         mIncrementalDir(rootDir) {
     if (!mVold) {
         LOG(FATAL) << "Vold service is unavailable";
@@ -238,6 +244,9 @@
     if (!mDataLoaderManager) {
         LOG(FATAL) << "DataLoaderManagerService is unavailable";
     }
+    if (!mAppOpsManager) {
+        LOG(FATAL) << "AppOpsManager is unavailable";
+    }
     mountExistingImages();
 }
 
@@ -275,12 +284,12 @@
         const IncFsMount& mnt = *ifs.get();
         dprintf(fd, "\t[%d]:\n", id);
         dprintf(fd, "\t\tmountId: %d\n", mnt.mountId);
+        dprintf(fd, "\t\troot: %s\n", mnt.root.c_str());
         dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
         dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load());
-        dprintf(fd, "\t\tconnectionLostTime: %s\n", toString(mnt.connectionLostTime));
-        if (mnt.savedDataLoaderParams) {
-            const auto& params = mnt.savedDataLoaderParams.value();
-            dprintf(fd, "\t\tsavedDataLoaderParams:\n");
+        {
+            const auto& params = mnt.dataLoaderParams;
+            dprintf(fd, "\t\tdataLoaderParams:\n");
             dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str());
             dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str());
             dprintf(fd, "\t\t\tclassName: %s\n", params.className.c_str());
@@ -328,6 +337,7 @@
     }
 
     std::thread([this, mounts = std::move(mounts)]() {
+        /* TODO(b/151241369): restore data loaders on reboot.
         for (auto&& ifs : mounts) {
             if (prepareDataLoader(*ifs)) {
                 LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId;
@@ -336,6 +346,7 @@
                 LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId;
             }
         }
+        */
         mPrepareDataLoaders.set_value_at_thread_exit();
     }).detach();
     return mPrepareDataLoaders.get_future();
@@ -455,13 +466,15 @@
         return kInvalidStorageId;
     }
 
+    ifs->dataLoaderParams = std::move(dataLoaderParams);
+
     {
         metadata::Mount m;
         m.mutable_storage()->set_id(ifs->mountId);
-        m.mutable_loader()->set_type((int)dataLoaderParams.type);
-        m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
-        m.mutable_loader()->set_class_name(dataLoaderParams.className);
-        m.mutable_loader()->set_arguments(dataLoaderParams.arguments);
+        m.mutable_loader()->set_type((int)ifs->dataLoaderParams.type);
+        m.mutable_loader()->set_package_name(ifs->dataLoaderParams.packageName);
+        m.mutable_loader()->set_class_name(ifs->dataLoaderParams.className);
+        m.mutable_loader()->set_arguments(ifs->dataLoaderParams.arguments);
         const auto metadata = m.SerializeAsString();
         m.mutable_loader()->release_arguments();
         m.mutable_loader()->release_class_name();
@@ -489,7 +502,7 @@
     // Done here as well, all data structures are in good state.
     secondCleanupOnFailure.release();
 
-    if (!prepareDataLoader(*ifs, &dataLoaderParams, &dataLoaderStatusListener)) {
+    if (!prepareDataLoader(*ifs, &dataLoaderStatusListener)) {
         LOG(ERROR) << "prepareDataLoader() failed";
         deleteStorageLocked(*ifs, std::move(l));
         return kInvalidStorageId;
@@ -569,11 +582,30 @@
         return -EINVAL;
     }
 
+    ifs->dataLoaderFilesystemParams.readLogsEnabled = enableReadLogs;
+    if (enableReadLogs) {
+        // We never unregister the callbacks, but given a restricted number of data loaders and even fewer asking for read log access, should be ok.
+        registerAppOpsCallback(ifs->dataLoaderParams.packageName);
+    }
+
+    return applyStorageParams(*ifs);
+}
+
+int IncrementalService::applyStorageParams(IncFsMount& ifs) {
+    const bool enableReadLogs = ifs.dataLoaderFilesystemParams.readLogsEnabled;
+    if (enableReadLogs) {
+        if (auto status = CheckPermissionForDataDelivery(kDataUsageStats, kOpUsage);
+            !status.isOk()) {
+            LOG(ERROR) << "CheckPermissionForDataDelivery failed: " << status.toString8();
+            return fromBinderStatus(status);
+        }
+    }
+
     using unique_fd = ::android::base::unique_fd;
     ::android::os::incremental::IncrementalFileSystemControlParcel control;
-    control.cmd.reset(unique_fd(dup(ifs->control.cmd())));
-    control.pendingReads.reset(unique_fd(dup(ifs->control.pendingReads())));
-    auto logsFd = ifs->control.logs();
+    control.cmd.reset(unique_fd(dup(ifs.control.cmd())));
+    control.pendingReads.reset(unique_fd(dup(ifs.control.pendingReads())));
+    auto logsFd = ifs.control.logs();
     if (logsFd >= 0) {
         control.log.reset(unique_fd(dup(logsFd)));
     }
@@ -582,12 +614,7 @@
     const auto status = mVold->setIncFsMountOptions(control, enableReadLogs);
     if (!status.isOk()) {
         LOG(ERROR) << "Calling Vold::setIncFsMountOptions() failed: " << status.toString8();
-        return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
-                ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode()
-                                                        : status.serviceSpecificErrorCode() == 0
-                                ? -EFAULT
-                                : status.serviceSpecificErrorCode()
-                : -EIO;
+        return fromBinderStatus(status);
     }
 
     return 0;
@@ -987,13 +1014,13 @@
             continue;
         }
         const auto root = path::join(mIncrementalDir, name);
-        if (!mountExistingImage(root, name)) {
+        if (!mountExistingImage(root)) {
             IncFsMount::cleanupFilesystem(path);
         }
     }
 }
 
-bool IncrementalService::mountExistingImage(std::string_view root, std::string_view key) {
+bool IncrementalService::mountExistingImage(std::string_view root) {
     auto mountTarget = path::join(root, constants().mount);
     const auto backing = path::join(root, constants().backing);
 
@@ -1011,16 +1038,26 @@
 
     auto ifs = std::make_shared<IncFsMount>(std::string(root), -1, std::move(control), *this);
 
-    auto m = parseFromIncfs<metadata::Mount>(mIncFs.get(), ifs->control,
-                                             path::join(mountTarget, constants().infoMdName));
-    if (!m.has_loader() || !m.has_storage()) {
+    auto mount = parseFromIncfs<metadata::Mount>(mIncFs.get(), ifs->control,
+                                                 path::join(mountTarget, constants().infoMdName));
+    if (!mount.has_loader() || !mount.has_storage()) {
         LOG(ERROR) << "Bad mount metadata in mount at " << root;
         return false;
     }
 
-    ifs->mountId = m.storage().id();
+    ifs->mountId = mount.storage().id();
     mNextId = std::max(mNextId, ifs->mountId + 1);
 
+    // DataLoader params
+    {
+        auto& dlp = ifs->dataLoaderParams;
+        const auto& loader = mount.loader();
+        dlp.type = (android::content::pm::DataLoaderType)loader.type();
+        dlp.packageName = loader.package_name();
+        dlp.className = loader.class_name();
+        dlp.arguments = loader.arguments();
+    }
+
     std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
     auto d = openDir(path::c_str(mountTarget));
     while (auto e = ::readdir(d.get())) {
@@ -1044,16 +1081,24 @@
             }
             auto name = std::string_view(e->d_name);
             if (name.starts_with(constants().storagePrefix)) {
-                auto md = parseFromIncfs<metadata::Storage>(mIncFs.get(), ifs->control,
-                                                            path::join(mountTarget, name));
-                auto [_, inserted] = mMounts.try_emplace(md.id(), ifs);
+                int storageId;
+                const auto res = std::from_chars(name.data() + constants().storagePrefix.size() + 1,
+                                                 name.data() + name.size(), storageId);
+                if (res.ec != std::errc{} || *res.ptr != '_') {
+                    LOG(WARNING) << "Ignoring storage with invalid name '" << name << "' for mount "
+                                 << root;
+                    continue;
+                }
+                auto [_, inserted] = mMounts.try_emplace(storageId, ifs);
                 if (!inserted) {
-                    LOG(WARNING) << "Ignoring storage with duplicate id " << md.id()
+                    LOG(WARNING) << "Ignoring storage with duplicate id " << storageId
                                  << " for mount " << root;
                     continue;
                 }
-                ifs->storages.insert_or_assign(md.id(), IncFsMount::Storage{std::string(name)});
-                mNextId = std::max(mNextId, md.id() + 1);
+                ifs->storages.insert_or_assign(storageId,
+                                               IncFsMount::Storage{
+                                                       path::join(root, constants().mount, name)});
+                mNextId = std::max(mNextId, storageId + 1);
             }
         }
     }
@@ -1083,23 +1128,9 @@
 }
 
 bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs,
-                                           DataLoaderParamsParcel* params,
                                            const DataLoaderStatusListener* externalListener) {
     if (!mSystemReady.load(std::memory_order_relaxed)) {
         std::unique_lock l(ifs.lock);
-        if (params) {
-            if (ifs.savedDataLoaderParams) {
-                LOG(WARNING) << "Trying to pass second set of data loader parameters, ignored it";
-            } else {
-                ifs.savedDataLoaderParams = std::move(*params);
-            }
-        } else {
-            if (!ifs.savedDataLoaderParams) {
-                LOG(ERROR) << "Mount " << ifs.mountId
-                           << " is broken: no data loader params (system is not ready yet)";
-                return false;
-            }
-        }
         return true; // eventually...
     }
 
@@ -1109,12 +1140,6 @@
         return true;
     }
 
-    auto* dlp = params ? params
-                       : ifs.savedDataLoaderParams ? &ifs.savedDataLoaderParams.value() : nullptr;
-    if (!dlp) {
-        LOG(ERROR) << "Mount " << ifs.mountId << " is broken: no data loader params";
-        return false;
-    }
     FileSystemControlParcel fsControlParcel;
     fsControlParcel.incremental = aidl::make_nullable<IncrementalFileSystemControlParcel>();
     fsControlParcel.incremental->cmd.reset(base::unique_fd(::dup(ifs.control.cmd())));
@@ -1126,13 +1151,11 @@
                                               externalListener ? *externalListener
                                                                : DataLoaderStatusListener());
     bool created = false;
-    auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, *dlp, fsControlParcel,
-                                                           listener, &created);
+    auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, ifs.dataLoaderParams, fsControlParcel, listener, &created);
     if (!status.isOk() || !created) {
         LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId;
         return false;
     }
-    ifs.savedDataLoaderParams.reset();
     return true;
 }
 
@@ -1256,6 +1279,42 @@
     return success;
 }
 
+void IncrementalService::registerAppOpsCallback(const std::string& packageName) {
+    if (packageName.empty()) {
+        return;
+    }
+
+    {
+        std::unique_lock lock{mCallbacksLock};
+        if (!mCallbackRegistered.insert(packageName).second) {
+            return;
+        }
+    }
+
+    /* TODO(b/152633648): restore callback after it's not crashing Binder anymore.
+    sp<AppOpsListener> listener = new AppOpsListener(*this, packageName);
+    mAppOpsManager->startWatchingMode(AppOpsManager::OP_GET_USAGE_STATS, String16(packageName.c_str()), listener);
+    */
+}
+
+void IncrementalService::onAppOppChanged(const std::string& packageName) {
+    std::vector<IfsMountPtr> affected;
+    {
+        std::lock_guard l(mLock);
+        affected.reserve(mMounts.size());
+        for (auto&& [id, ifs] : mMounts) {
+            if (ifs->dataLoaderFilesystemParams.readLogsEnabled && ifs->dataLoaderParams.packageName == packageName) {
+                affected.push_back(ifs);
+            }
+        }
+    }
+    /* TODO(b/152633648): restore callback after it's not crashing Kernel anymore.
+    for (auto&& ifs : affected) {
+        applyStorageParams(*ifs);
+    }
+    */
+}
+
 binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId,
                                                                                   int newStatus) {
     if (externalListener) {
@@ -1319,4 +1378,8 @@
     return binder::Status::ok();
 }
 
+void IncrementalService::AppOpsListener::opChanged(int32_t op, const String16&) {
+    incrementalService.onAppOppChanged(packageName);
+}
+
 } // namespace android::incremental
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 90d58a7..ff69633 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -40,6 +40,7 @@
 #include "ServiceWrappers.h"
 #include "android/content/pm/BnDataLoaderStatusListener.h"
 #include "incfs.h"
+#include "dataloader_ndk.h"
 #include "path.h"
 
 using namespace android::os::incremental;
@@ -132,6 +133,7 @@
     bool startLoading(StorageId storage) const;
     bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
                                  std::string_view libDirRelativePath, std::string_view abi);
+
     class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener {
     public:
         IncrementalDataLoaderListener(IncrementalService& incrementalService,
@@ -145,6 +147,16 @@
         DataLoaderStatusListener externalListener;
     };
 
+    class AppOpsListener : public android::BnAppOpsCallback {
+    public:
+        AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {}
+        void opChanged(int32_t op, const String16& packageName) override;
+
+    private:
+        IncrementalService& incrementalService;
+        const std::string packageName;
+    };
+
 private:
     struct IncFsMount {
         struct Bind {
@@ -169,11 +181,11 @@
         /*const*/ MountId mountId;
         StorageMap storages;
         BindMap bindPoints;
-        std::optional<DataLoaderParamsParcel> savedDataLoaderParams;
+        DataLoaderParamsParcel dataLoaderParams;
+        DataLoaderFilesystemParams dataLoaderFilesystemParams;
         std::atomic<int> nextStorageDirNo{0};
         std::atomic<int> dataLoaderStatus = -1;
         bool dataLoaderStartRequested = false;
-        TimePoint connectionLostTime = TimePoint();
         const IncrementalService& incrementalService;
 
         IncFsMount(std::string root, MountId mountId, Control control,
@@ -181,7 +193,9 @@
               : root(std::move(root)),
                 control(std::move(control)),
                 mountId(mountId),
-                incrementalService(incrementalService) {}
+                incrementalService(incrementalService) {
+            dataLoaderFilesystemParams.readLogsEnabled = false;
+        }
         IncFsMount(IncFsMount&&) = delete;
         IncFsMount& operator=(IncFsMount&&) = delete;
         ~IncFsMount();
@@ -196,7 +210,7 @@
     using BindPathMap = std::map<std::string, IncFsMount::BindMap::iterator, path::PathLess>;
 
     void mountExistingImages();
-    bool mountExistingImage(std::string_view root, std::string_view key);
+    bool mountExistingImage(std::string_view root);
 
     IfsMountPtr getIfs(StorageId storage) const;
     const IfsMountPtr& getIfsLocked(StorageId storage) const;
@@ -208,8 +222,7 @@
                            std::string&& source, std::string&& target, BindKind kind,
                            std::unique_lock<std::mutex>& mainLock);
 
-    bool prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel* params = nullptr,
-                           const DataLoaderStatusListener* externalListener = nullptr);
+    bool prepareDataLoader(IncFsMount& ifs, const DataLoaderStatusListener* externalListener = nullptr);
     bool startDataLoader(MountId mountId) const;
 
     BindPathMap::const_iterator findStorageLocked(std::string_view path) const;
@@ -221,10 +234,16 @@
     std::string normalizePathToStorage(const IfsMountPtr incfs, StorageId storage,
                                        std::string_view path);
 
+    int applyStorageParams(IncFsMount& ifs);
+
+    void registerAppOpsCallback(const std::string& packageName);
+    void onAppOppChanged(const std::string& packageName);
+
     // Member variables
-    std::unique_ptr<VoldServiceWrapper> mVold;
-    std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager;
-    std::unique_ptr<IncFsWrapper> mIncFs;
+    std::unique_ptr<VoldServiceWrapper> const mVold;
+    std::unique_ptr<DataLoaderManagerWrapper> const mDataLoaderManager;
+    std::unique_ptr<IncFsWrapper> const mIncFs;
+    std::unique_ptr<AppOpsManagerWrapper> const mAppOpsManager;
     const std::string mIncrementalDir;
 
     mutable std::mutex mLock;
@@ -232,6 +251,9 @@
     MountMap mMounts;
     BindPathMap mBindsByPath;
 
+    std::mutex mCallbacksLock;
+    std::set<std::string> mCallbackRegistered;
+
     std::atomic_bool mSystemReady = false;
     StorageId mNextId = 0;
     std::promise<void> mPrepareDataLoaders;
diff --git a/services/incremental/IncrementalServiceValidation.h b/services/incremental/IncrementalServiceValidation.h
new file mode 100644
index 0000000..24f9f7f
--- /dev/null
+++ b/services/incremental/IncrementalServiceValidation.h
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/stringprintf.h>
+#include <binder/IPCThreadState.h>
+#include <binder/PermissionCache.h>
+#include <binder/PermissionController.h>
+#include <binder/Status.h>
+
+namespace android::incremental {
+
+inline binder::Status Ok() {
+    return binder::Status::ok();
+}
+
+inline binder::Status Exception(uint32_t code, const std::string& msg) {
+    return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
+}
+
+inline int fromBinderStatus(const binder::Status& status) {
+    return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
+            ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode()
+                                                    : status.serviceSpecificErrorCode() == 0
+                            ? -EFAULT
+                            : status.serviceSpecificErrorCode()
+            : -EIO;
+}
+
+inline binder::Status CheckPermissionForDataDelivery(const char* permission, const char* operation) {
+    using android::base::StringPrintf;
+
+    int32_t pid;
+    int32_t uid;
+
+    if (!PermissionCache::checkCallingPermission(String16(permission), &pid, &uid)) {
+        return Exception(binder::Status::EX_SECURITY,
+                         StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, permission));
+    }
+
+    // Caller must also have op granted.
+    PermissionController pc;
+    // Package is a required parameter. Need to obtain one.
+    Vector<String16> packages;
+    pc.getPackagesForUid(uid, packages);
+    if (packages.empty()) {
+        return Exception(binder::Status::EX_SECURITY,
+                         StringPrintf("UID %d / PID %d has no packages", uid, pid));
+    }
+    switch (auto result = pc.noteOp(String16(operation), uid, packages[0]); result) {
+        case PermissionController::MODE_ALLOWED:
+        case PermissionController::MODE_DEFAULT:
+            return binder::Status::ok();
+        default:
+            return Exception(binder::Status::EX_SECURITY,
+                             StringPrintf("UID %d / PID %d lacks app-op %s, error %d", uid, pid,
+                                          operation, result));
+    }
+}
+
+} // namespace android::incremental
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 2e31ef1..9f4192f 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -59,4 +59,8 @@
     return std::make_unique<RealIncFs>();
 }
 
+std::unique_ptr<AppOpsManagerWrapper> RealServiceManager::getAppOpsManager() {
+    return std::make_unique<RealAppOpsManager>();
+}
+
 } // namespace android::os::incremental
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index c330030..449b457 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -24,6 +24,7 @@
 #include <android/content/pm/IDataLoaderManager.h>
 #include <android/content/pm/IDataLoaderStatusListener.h>
 #include <android/os/IVold.h>
+#include <binder/AppOpsManager.h>
 #include <binder/IServiceManager.h>
 #include <incfs.h>
 
@@ -81,12 +82,19 @@
     virtual ErrorCode writeBlocks(Span<const DataBlock> blocks) const = 0;
 };
 
+class AppOpsManagerWrapper {
+public:
+    virtual ~AppOpsManagerWrapper() = default;
+    virtual void startWatchingMode(int32_t op, const String16& packageName, const sp<IAppOpsCallback>& callback) = 0;
+};
+
 class ServiceManagerWrapper {
 public:
     virtual ~ServiceManagerWrapper() = default;
     virtual std::unique_ptr<VoldServiceWrapper> getVoldService() = 0;
     virtual std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() = 0;
     virtual std::unique_ptr<IncFsWrapper> getIncFs() = 0;
+    virtual std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() = 0;
 };
 
 // --- Real stuff ---
@@ -137,13 +145,24 @@
     sp<content::pm::IDataLoaderManager> mInterface;
 };
 
+class RealAppOpsManager : public AppOpsManagerWrapper {
+public:
+    ~RealAppOpsManager() = default;
+    void startWatchingMode(int32_t op, const String16& packageName, const sp<IAppOpsCallback>& callback) override {
+        mAppOpsManager.startWatchingMode(op, packageName, callback);
+    }
+private:
+    android::AppOpsManager mAppOpsManager;
+};
+
 class RealServiceManager : public ServiceManagerWrapper {
 public:
     RealServiceManager(sp<IServiceManager> serviceManager);
     ~RealServiceManager() = default;
-    std::unique_ptr<VoldServiceWrapper> getVoldService() override;
-    std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() override;
-    std::unique_ptr<IncFsWrapper> getIncFs() override;
+    std::unique_ptr<VoldServiceWrapper> getVoldService() final;
+    std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final;
+    std::unique_ptr<IncFsWrapper> getIncFs() final;
+    std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final;
 
 private:
     template <class INTERFACE>
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index cde38fb..5553f68 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -220,24 +220,32 @@
     }
 };
 
+class MockAppOpsManager : public AppOpsManagerWrapper {
+    MOCK_METHOD3(startWatchingMode, void(int32_t, const String16&, const sp<IAppOpsCallback>&));
+};
+
 class MockServiceManager : public ServiceManagerWrapper {
 public:
     MockServiceManager(std::unique_ptr<MockVoldService> vold,
                        std::unique_ptr<MockDataLoaderManager> manager,
-                       std::unique_ptr<MockIncFs> incfs)
+                       std::unique_ptr<MockIncFs> incfs,
+                       std::unique_ptr<MockAppOpsManager> appOpsManager)
           : mVold(std::move(vold)),
             mDataLoaderManager(std::move(manager)),
-            mIncFs(std::move(incfs)) {}
+            mIncFs(std::move(incfs)),
+            mAppOpsManager(std::move(appOpsManager)) {}
     std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); }
     std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final {
         return std::move(mDataLoaderManager);
     }
     std::unique_ptr<IncFsWrapper> getIncFs() final { return std::move(mIncFs); }
+    std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final { return std::move(mAppOpsManager); }
 
 private:
     std::unique_ptr<MockVoldService> mVold;
     std::unique_ptr<MockDataLoaderManager> mDataLoaderManager;
     std::unique_ptr<MockIncFs> mIncFs;
+    std::unique_ptr<MockAppOpsManager> mAppOpsManager;
 };
 
 // --- IncrementalServiceTest ---
@@ -251,11 +259,13 @@
         mDataLoaderManager = dataloaderManager.get();
         auto incFs = std::make_unique<NiceMock<MockIncFs>>();
         mIncFs = incFs.get();
+        auto appOps = std::make_unique<NiceMock<MockAppOpsManager>>();
+        mAppOpsManager = appOps.get();
         mIncrementalService =
                 std::make_unique<IncrementalService>(MockServiceManager(std::move(vold),
-                                                                        std::move(
-                                                                                dataloaderManager),
-                                                                        std::move(incFs)),
+                                                                        std::move(dataloaderManager),
+                                                                        std::move(incFs),
+                                                                        std::move(appOps)),
                                                      mRootDir.path);
         mDataLoaderParcel.packageName = "com.test";
         mDataLoaderParcel.arguments = "uri";
@@ -287,6 +297,7 @@
     NiceMock<MockVoldService>* mVold;
     NiceMock<MockIncFs>* mIncFs;
     NiceMock<MockDataLoaderManager>* mDataLoaderManager;
+    NiceMock<MockAppOpsManager>* mAppOpsManager;
     std::unique_ptr<IncrementalService> mIncrementalService;
     TemporaryDir mRootDir;
     DataLoaderParamsParcel mDataLoaderParcel;
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
index 5480b6c..c225d3f 100644
--- a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
@@ -108,7 +108,8 @@
             }
             mBinding = true;
 
-            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                    | Context.BIND_INCLUDE_CAPABILITIES;
             boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags,
                     mHandler, new UserHandle(mUserId));
             if (!willBind) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java
new file mode 100644
index 0000000..4fe9458
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java
@@ -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.server.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.BiometricAuthenticator;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+public class BiometricServiceBaseTest {
+    private static class TestableBiometricServiceBase extends BiometricServiceBase {
+        TestableBiometricServiceBase(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected String getTag() {
+            return null;
+        }
+
+        @Override
+        protected DaemonWrapper getDaemonWrapper() {
+            return null;
+        }
+
+        @Override
+        protected BiometricUtils getBiometricUtils() {
+            return null;
+        }
+
+        @Override
+        protected Constants getConstants() {
+            return null;
+        }
+
+        @Override
+        protected boolean hasReachedEnrollmentLimit(int userId) {
+            return false;
+        }
+
+        @Override
+        protected void updateActiveGroup(int userId, String clientPackage) {
+        }
+
+        @Override
+        protected String getLockoutResetIntent() {
+            return null;
+        }
+
+        @Override
+        protected String getLockoutBroadcastPermission() {
+            return null;
+        }
+
+        @Override
+        protected long getHalDeviceId() {
+            return 0;
+        }
+
+        @Override
+        protected boolean hasEnrolledBiometrics(int userId) {
+            return false;
+        }
+
+        @Override
+        protected String getManageBiometricPermission() {
+            return null;
+        }
+
+        @Override
+        protected void checkUseBiometricPermission() {
+        }
+
+        @Override
+        protected boolean checkAppOps(int uid, String opPackageName) {
+            return false;
+        }
+
+        @Override
+        protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(
+                int userId) {
+            return null;
+        }
+
+        @Override
+        protected int statsModality() {
+            return 0;
+        }
+
+        @Override
+        protected int getLockoutMode() {
+            return 0;
+        }
+    }
+
+    private static final int CLIENT_COOKIE = 0xc00c1e;
+
+    private BiometricServiceBase mBiometricServiceBase;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private BiometricAuthenticator.Identifier mIdentifier;
+    @Mock
+    private ClientMonitor mClient;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getString(anyInt())).thenReturn("");
+        when(mClient.getCookie()).thenReturn(CLIENT_COOKIE);
+
+        mBiometricServiceBase = new TestableBiometricServiceBase(mContext);
+    }
+
+    @Test
+    public void testHandleEnumerate_doesNotCrash_withNullClient() {
+        mBiometricServiceBase.handleEnumerate(mIdentifier, 0 /* remaining */);
+    }
+
+    @Test
+    public void testStartClient_sendsErrorAndRemovesClient_onNonzeroErrorCode() {
+        when(mClient.start()).thenReturn(1);
+
+        mBiometricServiceBase.startClient(mClient, false /* initiatedByClient */);
+
+        verify(mClient).onError(anyLong(), anyInt(), anyInt());
+        verify(mClient).destroy();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 7027185..7ad39b4 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -34,14 +34,17 @@
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
 import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.InputManagerInternal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.Surface;
 import android.view.SurfaceControl;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -67,6 +70,8 @@
 public class DisplayManagerServiceTest {
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
     private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
+    private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
+    private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
 
     private Context mContext;
 
@@ -97,6 +102,7 @@
 
     @Mock InputManagerInternal mMockInputManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
+    @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock LightsManager mMockLightsManager;
     @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
@@ -135,10 +141,12 @@
         int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
 
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        int displayId = bs.createVirtualDisplay(mMockAppToken /* callback */,
-                null /* projection */, "com.android.frameworks.servicestests",
-                "Test Virtual Display", width, height, dpi, null /* surface */, flags /* flags */,
-                uniqueId);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setUniqueId(uniqueId);
+        builder.setFlags(flags);
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -241,10 +249,12 @@
         int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
 
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        int displayId = bs.createVirtualDisplay(mMockAppToken /* callback */,
-                null /* projection */, "com.android.frameworks.servicestests",
-                "Test Virtual Display", width, height, dpi, null /* surface */, flags /* flags */,
-                uniqueId);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -409,6 +419,87 @@
         assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
     }
 
+    /**
+     * Tests that the virtual display is created with
+     * {@link VirtualDisplayConfig.Builder#setDisplayIdToMirror(int)}
+     */
+    @Test
+    @FlakyTest(bugId = 127687569)
+    public void testCreateVirtualDisplay_displayIdToMirror() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+
+        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setUniqueId(uniqueId);
+        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        // The second virtual display requests to mirror the first virtual display.
+        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
+        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2);
+        builder2.setUniqueId(uniqueId2);
+        builder2.setDisplayIdToMirror(firstDisplayId);
+        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
+                mMockAppToken2 /* callback */, null /* projection */, PACKAGE_NAME);
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        // The displayId to mirror should be a default display if there is none initially.
+        assertEquals(displayManager.getDisplayIdToMirrorInternal(firstDisplayId),
+                Display.DEFAULT_DISPLAY);
+        assertEquals(displayManager.getDisplayIdToMirrorInternal(secondDisplayId),
+                firstDisplayId);
+    }
+
+    /**
+     * Tests that the virtual display is created with
+     * {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
+     */
+    @Test
+    @FlakyTest(bugId = 127687569)
+    public void testCreateVirtualDisplay_setSurface() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+
+        final String uniqueId = "uniqueId --- setSurface";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+        final Surface surface = new Surface();
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setSurface(surface);
+        builder.setUniqueId(uniqueId);
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface);
+    }
+
     private void registerDefaultDisplays(DisplayManagerService displayManager) {
         Handler handler = displayManager.getDisplayHandler();
         // Would prefer to call displayManager.onStart() directly here but it performs binderService
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index 05604b2..1debd8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -61,19 +61,20 @@
     @Test
     public void testLastFocusedStackIsUpdatedWhenMovingStack() {
         // Create a stack at bottom.
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
+        final TaskDisplayArea taskDisplayAreas =
+                mRootWindowContainer.getDefaultDisplay().getDefaultTaskDisplayArea();
         final ActivityStack stack =
                 new StackBuilder(mRootWindowContainer).setOnTop(!ON_TOP).build();
-        final ActivityStack prevFocusedStack = display.getFocusedStack();
+        final ActivityStack prevFocusedStack = taskDisplayAreas.getFocusedStack();
 
         stack.moveToFront("moveStackToFront");
         // After moving the stack to front, the previous focused should be the last focused.
         assertTrue(stack.isFocusedStackOnDisplay());
-        assertEquals(prevFocusedStack, display.mTaskContainers.getLastFocusedStack());
+        assertEquals(prevFocusedStack, taskDisplayAreas.getLastFocusedStack());
 
         stack.moveToBack("moveStackToBack", null /* task */);
         // After moving the stack to back, the stack should be the last focused.
-        assertEquals(stack, display.mTaskContainers.getLastFocusedStack());
+        assertEquals(stack, taskDisplayAreas.getLastFocusedStack());
     }
 
     /**
@@ -83,8 +84,8 @@
     @Test
     public void testFullscreenStackCanBeFocusedWhenFocusablePinnedStackExists() {
         // Create a pinned stack and move to front.
-        final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
-                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
+        final ActivityStack pinnedStack = mRootWindowContainer.getDefaultTaskDisplayArea()
+                .createStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
         final Task pinnedTask = new TaskBuilder(mService.mStackSupervisor)
                 .setStack(pinnedStack).build();
         new ActivityBuilder(mService).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
@@ -104,8 +105,8 @@
     }
 
     /**
-     * Test {@link DisplayContent#mPreferredTopFocusableStack} will be cleared when the stack is
-     * removed or moved to back, and the focused stack will be according to z-order.
+     * Test {@link TaskDisplayArea#mPreferredTopFocusableStack} will be cleared when
+     * the stack is removed or moved to back, and the focused stack will be according to z-order.
      */
     @Test
     public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() {
@@ -124,7 +125,7 @@
         assertTrue(stack1.isFocusedStackOnDisplay());
 
         // Stack2 should be focused after removing stack1.
-        display.removeStack(stack1);
+        stack1.getDisplayArea().removeStack(stack1);
         assertTrue(stack2.isFocusedStackOnDisplay());
     }
 
@@ -156,7 +157,7 @@
     }
 
     private ActivityStack createFullscreenStackWithSimpleActivityAt(DisplayContent display) {
-        final ActivityStack fullscreenStack = display.createStack(
+        final ActivityStack fullscreenStack = display.getDefaultTaskDisplayArea().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);
         final Task fullscreenTask = new TaskBuilder(mService.mStackSupervisor)
                 .setStack(fullscreenStack).build();
@@ -219,58 +220,56 @@
      */
     @Test
     public void testAlwaysOnTopStackLocation() {
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-        final ActivityStack alwaysOnTopStack = display.createStack(WINDOWING_MODE_FREEFORM,
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        final ActivityStack alwaysOnTopStack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
                 .setStack(alwaysOnTopStack).build();
         alwaysOnTopStack.setAlwaysOnTop(true);
-        display.mTaskContainers.positionStackAtTop(alwaysOnTopStack, false /* includingParents */);
+        taskDisplayArea.positionStackAtTop(alwaysOnTopStack, false /* includingParents */);
         assertTrue(alwaysOnTopStack.isAlwaysOnTop());
         // Ensure always on top state is synced to the children of the stack.
         assertTrue(alwaysOnTopStack.getTopNonFinishingActivity().isAlwaysOnTop());
-        assertEquals(alwaysOnTopStack, display.getTopStack());
+        assertEquals(alwaysOnTopStack, taskDisplayArea.getTopStack());
 
-        final ActivityStack pinnedStack = display.createStack(
+        final ActivityStack pinnedStack = taskDisplayArea.createStack(
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        assertEquals(pinnedStack, display.getRootPinnedTask());
-        assertEquals(pinnedStack, display.getTopStack());
+        assertEquals(pinnedStack, taskDisplayArea.getRootPinnedTask());
+        assertEquals(pinnedStack, taskDisplayArea.getTopStack());
 
-        final ActivityStack anotherAlwaysOnTopStack = display.createStack(
+        final ActivityStack anotherAlwaysOnTopStack = taskDisplayArea.createStack(
                 WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         anotherAlwaysOnTopStack.setAlwaysOnTop(true);
-        display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack,
-                false /* includingParents */);
+        taskDisplayArea.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */);
         assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop());
-        int topPosition = display.getStackCount() - 1;
+        int topPosition = taskDisplayArea.getStackCount() - 1;
         // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the
         // existing alwaysOnTop stack.
-        assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1));
+        assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 1));
 
-        final ActivityStack nonAlwaysOnTopStack = display.createStack(
+        final ActivityStack nonAlwaysOnTopStack = taskDisplayArea.createStack(
                 WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        assertEquals(display, nonAlwaysOnTopStack.getDisplay());
-        topPosition = display.getStackCount() - 1;
+        assertEquals(taskDisplayArea, nonAlwaysOnTopStack.getDisplayArea());
+        topPosition = taskDisplayArea.getStackCount() - 1;
         // Ensure the non-alwaysOnTop stack is put below the three alwaysOnTop stacks, but above the
         // existing other non-alwaysOnTop stacks.
-        assertEquals(nonAlwaysOnTopStack, display.getStackAt(topPosition - 3));
+        assertEquals(nonAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 3));
 
         anotherAlwaysOnTopStack.setAlwaysOnTop(false);
-        display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack,
-                false /* includingParents */);
+        taskDisplayArea.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */);
         assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop());
         // Ensure, when always on top is turned off for a stack, the stack is put just below all
         // other always on top stacks.
-        assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2));
+        assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 2));
         anotherAlwaysOnTopStack.setAlwaysOnTop(true);
 
         // Ensure always on top state changes properly when windowing mode changes.
         anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop());
-        assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2));
+        assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 2));
         anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FREEFORM);
         assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop());
-        assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1));
+        assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 1));
     }
 
     @Test
@@ -286,14 +285,14 @@
     }
 
     private void removeStackTests(Runnable runnable) {
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-        final ActivityStack stack1 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        final ActivityStack stack1 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
-        final ActivityStack stack2 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final ActivityStack stack2 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
-        final ActivityStack stack3 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final ActivityStack stack3 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
-        final ActivityStack stack4 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final ActivityStack stack4 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
         final Task task1 = new TaskBuilder(mService.mStackSupervisor).setStack(stack1).build();
         final Task task2 = new TaskBuilder(mService.mStackSupervisor).setStack(stack2).build();
@@ -302,13 +301,13 @@
 
         // Reordering stacks while removing stacks.
         doAnswer(invocation -> {
-            display.mTaskContainers.positionStackAtTop(stack3, false);
+            taskDisplayArea.positionStackAtTop(stack3, false);
             return true;
         }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
 
         // Removing stacks from the display while removing stacks.
         doAnswer(invocation -> {
-            display.removeStack(stack2);
+            taskDisplayArea.removeStack(stack2);
             return true;
         }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 747ae94..08f6409 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -82,6 +82,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.MergedConfiguration;
 import android.util.MutableBoolean;
+import android.view.DisplayInfo;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner.Stub;
 import android.view.RemoteAnimationAdapter;
@@ -399,6 +400,16 @@
         mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
         final Rect stableRect = new Rect();
         mStack.getDisplay().mDisplayContent.getStableRect(stableRect);
+
+        // Carve out non-decor insets from stableRect
+        final Rect insets = new Rect();
+        final DisplayInfo displayInfo = mStack.getDisplay().getDisplayInfo();
+        final DisplayPolicy policy = mStack.getDisplay().getDisplayPolicy();
+        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+                displayInfo.logicalHeight, displayInfo.displayCutout, insets);
+        policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
+        Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
+
         final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
         final Rect bounds = new Rect(stableRect);
         if (isScreenPortrait) {
@@ -427,7 +438,17 @@
     public void ignoreRequestedOrientationInSplitWindows() {
         mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final Rect stableRect = new Rect();
-        mStack.getDisplay().mDisplayContent.getStableRect(stableRect);
+        mStack.getDisplay().getStableRect(stableRect);
+
+        // Carve out non-decor insets from stableRect
+        final Rect insets = new Rect();
+        final DisplayInfo displayInfo = mStack.getDisplay().getDisplayInfo();
+        final DisplayPolicy policy = mStack.getDisplay().getDisplayPolicy();
+        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+                displayInfo.logicalHeight, displayInfo.displayCutout, insets);
+        policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
+        Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
+
         final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
         final Rect bounds = new Rect(stableRect);
         if (isScreenPortrait) {
@@ -1379,8 +1400,8 @@
             display = new TestDisplayContent.Builder(mService, 2000, 1000).setDensityDpi(300)
                     .setPosition(DisplayContent.POSITION_TOP).build();
         }
-        final ActivityStack stack = display.createStack(WINDOWING_MODE_UNDEFINED,
-                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityStack stack = display.getDefaultTaskDisplayArea()
+                .createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
         return new ActivityBuilder(mService).setTask(task).setUseProcess(process).build();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index e8c0362..22d7fcb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -60,7 +60,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mFullscreenStack = mRootWindowContainer.getDefaultDisplay().createStack(
+        mFullscreenStack = mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index af5afff..3d15401 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -82,15 +82,15 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class ActivityStackTests extends ActivityTestsBase {
-    private DisplayContent mDefaultDisplay;
+    private TaskDisplayArea mDefaultTaskDisplayArea;
     private ActivityStack mStack;
     private Task mTask;
 
     @Before
     public void setUp() throws Exception {
-        mDefaultDisplay = mRootWindowContainer.getDefaultDisplay();
-        mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD,
-                true /* onTop */);
+        mDefaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        mStack = mDefaultTaskDisplayArea.createStack(WINDOWING_MODE_UNDEFINED,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
         spyOn(mStack);
         mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
     }
@@ -112,7 +112,7 @@
         r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
         assertEquals(r, mStack.getResumedActivity());
 
-        final ActivityStack destStack = mRootWindowContainer.getDefaultDisplay().createStack(
+        final ActivityStack destStack = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         mTask.reparent(destStack, true /* toTop */, Task.REPARENT_KEEP_STACK_AT_FRONT,
@@ -130,7 +130,7 @@
         r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
         assertEquals(r, mStack.getResumedActivity());
 
-        final ActivityStack destStack = mRootWindowContainer.getDefaultDisplay().createStack(
+        final ActivityStack destStack = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_STACK_TO_FRONT, false, false,
                 "testResumedActivityFromActivityReparenting");
@@ -143,7 +143,7 @@
     public void testPrimarySplitScreenRestoresWhenMovedToBack() {
         // Create primary splitscreen stack. This will create secondary stacks and places the
         // existing fullscreen stack on the bottom.
-        final ActivityStack primarySplitScreen = mDefaultDisplay.createStack(
+        final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         // Assert windowing mode.
@@ -154,7 +154,7 @@
                 null /* task */);
 
         // Assert that stack is at the bottom.
-        assertEquals(0, mDefaultDisplay.getIndexOf(primarySplitScreen));
+        assertEquals(0, mDefaultTaskDisplayArea.getIndexOf(primarySplitScreen));
 
         // Ensure no longer in splitscreen.
         assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode());
@@ -167,7 +167,7 @@
     @Test
     public void testPrimarySplitScreenRestoresPreviousWhenMovedToBack() {
         // This time, start with a fullscreen activitystack
-        final ActivityStack primarySplitScreen = mDefaultDisplay.createStack(
+        final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         primarySplitScreen.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -180,7 +180,7 @@
                 null /* task */);
 
         // Assert that stack is at the bottom.
-        assertEquals(0, mDefaultDisplay.getIndexOf(primarySplitScreen));
+        assertEquals(0, mDefaultTaskDisplayArea.getIndexOf(primarySplitScreen));
 
         // Ensure that the override mode is restored to what it was (fullscreen)
         assertEquals(WINDOWING_MODE_FULLSCREEN,
@@ -189,14 +189,14 @@
 
     @Test
     public void testStackInheritsDisplayWindowingMode() {
-        final ActivityStack primarySplitScreen = mDefaultDisplay.createStack(
+        final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode());
         assertEquals(WINDOWING_MODE_UNDEFINED,
                 primarySplitScreen.getRequestedOverrideWindowingMode());
 
-        mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        mDefaultTaskDisplayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
         assertEquals(WINDOWING_MODE_FREEFORM, primarySplitScreen.getWindowingMode());
         assertEquals(WINDOWING_MODE_UNDEFINED,
                 primarySplitScreen.getRequestedOverrideWindowingMode());
@@ -204,7 +204,7 @@
 
     @Test
     public void testStackOverridesDisplayWindowingMode() {
-        final ActivityStack primarySplitScreen = mDefaultDisplay.createStack(
+        final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode());
@@ -216,7 +216,7 @@
         assertEquals(WINDOWING_MODE_FULLSCREEN,
                 primarySplitScreen.getRequestedOverrideWindowingMode());
 
-        mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        mDefaultTaskDisplayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
         assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode());
     }
 
@@ -283,10 +283,11 @@
 
     @Test
     public void testMoveStackToBackIncludingParent() {
-        final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        final ActivityStack stack1 = createStackForShouldBeVisibleTest(display,
+        final TaskDisplayArea taskDisplayArea = addNewDisplayContentAt(DisplayContent.POSITION_TOP)
+                .getDefaultTaskDisplayArea();
+        final ActivityStack stack1 = createStackForShouldBeVisibleTest(taskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityStack stack2 = createStackForShouldBeVisibleTest(display,
+        final ActivityStack stack2 = createStackForShouldBeVisibleTest(taskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         // Do not move display to back because there is still another stack.
@@ -294,16 +295,16 @@
         verify(stack2).positionChildAtBottom(any(), eq(false) /* includingParents */);
 
         // Also move display to back because there is only one stack left.
-        display.removeStack(stack1);
+        taskDisplayArea.removeStack(stack1);
         stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask());
         verify(stack2).positionChildAtBottom(any(), eq(true) /* includingParents */);
     }
 
     @Test
     public void testShouldBeVisible_Fullscreen() {
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
-        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         // Add an activity to the pinned stack so it isn't considered empty for visibility check.
         final ActivityRecord pinnedActivity = new ActivityBuilder(mService)
@@ -314,8 +315,9 @@
         assertTrue(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
 
-        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
         // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
         // should be visible since it is always on-top.
         doReturn(false).when(fullscreenStack).isTranslucent(any());
@@ -331,15 +333,15 @@
 
     @Test
     public void testShouldBeVisible_SplitScreen() {
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         // Home stack should always be fullscreen for this test.
         doReturn(false).when(homeStack).supportsSplitScreenWindowingMode();
         final ActivityStack splitScreenPrimary =
-                createStackForShouldBeVisibleTest(mDefaultDisplay,
+                createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityStack splitScreenSecondary =
-                createStackForShouldBeVisibleTest(mDefaultDisplay,
+                createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         // Home stack shouldn't be visible if both halves of split-screen are opaque.
@@ -367,7 +369,7 @@
                 splitScreenSecondary.getVisibility(null /* starting */));
 
         final ActivityStack splitScreenSecondary2 =
-                createStackForShouldBeVisibleTest(mDefaultDisplay,
+                createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         // First split-screen secondary shouldn't be visible behind another opaque split-split
         // secondary.
@@ -389,8 +391,9 @@
         assertEquals(STACK_VISIBILITY_VISIBLE,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
-        final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+        final ActivityStack assistantStack = createStackForShouldBeVisibleTest(
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT,
+                true /* onTop */);
 
         // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
         doReturn(false).when(assistantStack).isTranslucent(any());
@@ -530,7 +533,7 @@
         final ActivityStack translucentStack =
                 createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         true /* translucent */);
-        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
@@ -547,7 +550,7 @@
 
     @Test
     public void testShouldBeVisible_Finishing() {
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
         if (topRunningHomeActivity == null) {
@@ -558,7 +561,7 @@
         }
 
         final ActivityStack translucentStack = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         doReturn(true).when(translucentStack).isTranslucent(any());
 
@@ -580,7 +583,7 @@
 
     @Test
     public void testShouldBeVisible_FullscreenBehindTranslucentInHomeStack() {
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
         final ActivityRecord firstActivity = new ActivityBuilder(mService)
@@ -601,74 +604,77 @@
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
-        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
 
         doReturn(false).when(homeStack).isTranslucent(any());
         doReturn(false).when(fullscreenStack).isTranslucent(any());
 
         // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
-        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
+        int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack);
         assertEquals(fullscreenStack, getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
-        assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
+        mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack));
     }
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
-        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(
+                mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         doReturn(false).when(homeStack).isTranslucent(any());
         doReturn(true).when(fullscreenStack).isTranslucent(any());
 
         // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
-        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
+        int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack);
         assertEquals(fullscreenStack, getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
-        assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
+        mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack));
     }
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
-        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
         doReturn(false).when(homeStack).isTranslucent(any());
         doReturn(false).when(fullscreenStack).isTranslucent(any());
 
         // Ensure we don't move the home stack if it is already on top
-        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
+        int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack);
         assertNull(getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
-        assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
+        mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack));
     }
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
-        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         doReturn(false).when(homeStack).isTranslucent(any());
@@ -678,22 +684,22 @@
         // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the
         // pinned stack
         assertEquals(fullscreenStack1, getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
+        mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack);
         assertEquals(fullscreenStack2, getStackAbove(homeStack));
     }
 
     @Test
     public void
             testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
 
         doReturn(false).when(homeStack).isTranslucent(any());
@@ -703,21 +709,21 @@
         // Ensure that we move the home stack behind the bottom most non-translucent fullscreen
         // stack
         assertEquals(fullscreenStack1, getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
+        mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack);
         assertEquals(fullscreenStack1, getStackAbove(homeStack));
     }
 
     @Test
     public void testMoveHomeStackBehindStack_BehindHomeStack() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
         final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
         doReturn(false).when(homeStack).isTranslucent(any());
@@ -725,50 +731,50 @@
         doReturn(false).when(fullscreenStack2).isTranslucent(any());
 
         // Ensure we don't move the home stack behind itself
-        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
-        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, homeStack);
-        assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
+        int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack);
+        mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, homeStack);
+        assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack));
     }
 
     @Test
     public void testMoveHomeStackBehindStack() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
         final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         final ActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         final ActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
-        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack1);
+        mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack1);
         assertEquals(fullscreenStack1, getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2);
+        mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack2);
         assertEquals(fullscreenStack2, getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack4);
+        mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack4);
         assertEquals(fullscreenStack4, getStackAbove(homeStack));
-        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2);
+        mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack2);
         assertEquals(fullscreenStack2, getStackAbove(homeStack));
     }
 
     @Test
     public void testSetAlwaysOnTop() {
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
-        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         assertEquals(pinnedStack, getStackAbove(homeStack));
 
         final ActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         alwaysOnTopStack.setAlwaysOnTop(true);
         assertTrue(alwaysOnTopStack.isAlwaysOnTop());
@@ -776,13 +782,13 @@
         assertEquals(pinnedStack, getStackAbove(alwaysOnTopStack));
 
         final ActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         // Ensure non always on top stack is put below always on top stacks.
         assertEquals(alwaysOnTopStack, getStackAbove(nonAlwaysOnTopStack));
 
         final ActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         alwaysOnTopStack2.setAlwaysOnTop(true);
         assertTrue(alwaysOnTopStack2.isAlwaysOnTop());
@@ -807,13 +813,14 @@
     @Test
     public void testSplitScreenMoveToFront() {
         final ActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
-                true /* onTop */);
+                mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(
-                mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityStack assistantStack = createStackForShouldBeVisibleTest(
+                mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT,
                 true /* onTop */);
-        final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
 
         doReturn(false).when(splitScreenPrimary).isTranslucent(any());
         doReturn(false).when(splitScreenSecondary).isTranslucent(any());
@@ -832,7 +839,7 @@
 
     private ActivityStack createStandardStackForVisibilityTest(int windowingMode,
             boolean translucent) {
-        final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         doReturn(translucent).when(stack).isTranslucent(any());
         return stack;
@@ -840,20 +847,20 @@
 
     @SuppressWarnings("TypeParameterUnusedInFormals")
     private ActivityStack createStackForShouldBeVisibleTest(
-            DisplayContent display, int windowingMode, int activityType, boolean onTop) {
+            TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, boolean onTop) {
         final ActivityStack stack;
         if (activityType == ACTIVITY_TYPE_HOME) {
             // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService
-            stack = mDefaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
+            stack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
             if (onTop) {
-                mDefaultDisplay.mTaskContainers.positionStackAtTop(stack,
+                mDefaultTaskDisplayArea.positionStackAtTop(stack,
                         false /* includingParents */);
             } else {
-                mDefaultDisplay.mTaskContainers.positionStackAtBottom(stack);
+                mDefaultTaskDisplayArea.positionStackAtBottom(stack);
             }
         } else {
             stack = new StackBuilder(mRootWindowContainer)
-                    .setDisplay(display)
+                    .setTaskDisplayArea(taskDisplayArea)
                     .setWindowingMode(windowingMode)
                     .setActivityType(activityType)
                     .setOnTop(onTop)
@@ -1005,7 +1012,7 @@
 
     @Test
     public void testWontFinishHomeStackImmediately() {
-        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+        final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
         ActivityRecord activity = homeStack.topRunningActivity();
@@ -1025,9 +1032,11 @@
     public void testFinishCurrentActivity() {
         // Create 2 activities on a new display.
         final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        final ActivityStack stack1 = createStackForShouldBeVisibleTest(display,
+        final ActivityStack stack1 = createStackForShouldBeVisibleTest(
+                display.getDefaultTaskDisplayArea(),
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityStack stack2 = createStackForShouldBeVisibleTest(display,
+        final ActivityStack stack2 = createStackForShouldBeVisibleTest(
+                display.getDefaultTaskDisplayArea(),
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         // There is still an activity1 in stack1 so the activity2 should be added to finishing list
@@ -1075,26 +1084,26 @@
     @Test
     public void testStackOrderChangedOnRemoveStack() {
         StackOrderChangedListener listener = new StackOrderChangedListener();
-        mDefaultDisplay.registerStackOrderChangedListener(listener);
+        mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener);
         try {
-            mDefaultDisplay.removeStack(mStack);
+            mDefaultTaskDisplayArea.removeStack(mStack);
         } finally {
-            mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+            mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener);
         }
         assertTrue(listener.mChanged);
     }
 
     @Test
     public void testStackOrderChangedOnAddPositionStack() {
-        mDefaultDisplay.removeStack(mStack);
+        mDefaultTaskDisplayArea.removeStack(mStack);
 
         StackOrderChangedListener listener = new StackOrderChangedListener();
-        mDefaultDisplay.registerStackOrderChangedListener(listener);
+        mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener);
         try {
             mStack.mReparenting = true;
-            mDefaultDisplay.mTaskContainers.addStack(mStack, 0);
+            mDefaultTaskDisplayArea.addStack(mStack, 0);
         } finally {
-            mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+            mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener);
         }
         assertTrue(listener.mChanged);
     }
@@ -1104,12 +1113,12 @@
         StackOrderChangedListener listener = new StackOrderChangedListener();
         try {
             final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
-                    mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                    mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                     true /* onTop */);
-            mDefaultDisplay.registerStackOrderChangedListener(listener);
-            mDefaultDisplay.mTaskContainers.positionStackAtBottom(fullscreenStack1);
+            mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener);
+            mDefaultTaskDisplayArea.positionStackAtBottom(fullscreenStack1);
         } finally {
-            mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+            mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener);
         }
         assertTrue(listener.mChanged);
     }
@@ -1189,7 +1198,7 @@
     @Test
     public void testClearUnknownAppVisibilityBehindFullscreenActivity() {
         final UnknownAppVisibilityController unknownAppVisibilityController =
-                mDefaultDisplay.mDisplayContent.mUnknownAppVisibilityController;
+                mDefaultTaskDisplayArea.mDisplayContent.mUnknownAppVisibilityController;
         final KeyguardController keyguardController = mSupervisor.getKeyguardController();
         doReturn(true).when(keyguardController).isKeyguardLocked();
 
@@ -1254,7 +1263,7 @@
     }
 
     private static class StackOrderChangedListener
-            implements DisplayContent.OnStackOrderChangedListener {
+            implements TaskDisplayArea.OnStackOrderChangedListener {
         public boolean mChanged = false;
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
index 76a761c..27782f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
@@ -77,8 +77,8 @@
                 .setCreateTask(true)
                 .build();
         final int startFlags = random.nextInt();
-        final ActivityStack stack = mService.mRootWindowContainer.getDefaultDisplay().createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityStack stack = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
+                .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final WindowProcessController wpc = new WindowProcessController(mService,
                 mService.mContext.getApplicationInfo(), "name", 12345,
                 UserHandle.getUserId(12345), mock(Object.class),
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1d952bf..1cca207 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -326,8 +326,9 @@
 
         if (mockGetLaunchStack) {
             // Instrument the stack and task used.
-            final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack(
-                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+            final ActivityStack stack = mRootWindowContainer.getDefaultTaskDisplayArea()
+                    .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                            true /* onTop */);
 
             // Direct starter to use spy stack.
             doReturn(stack).when(mRootWindowContainer)
@@ -742,7 +743,7 @@
         final TestDisplayContent secondaryDisplay =
                 new TestDisplayContent.Builder(mService, 1000, 1500)
                         .setPosition(POSITION_BOTTOM).build();
-        final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers;
+        final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea();
         final ActivityStack stack = secondaryTaskContainer.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
@@ -783,7 +784,7 @@
                 new TestDisplayContent.Builder(mService, 1000, 1500).build();
         mRootWindowContainer.positionChildAt(POSITION_TOP, secondaryDisplay,
                 false /* includingParents */);
-        final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers;
+        final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea();
         final ActivityRecord singleTaskActivity = createSingleTaskActivityOn(
                 secondaryTaskContainer.createStack(WINDOWING_MODE_FULLSCREEN,
                         ACTIVITY_TYPE_STANDARD, false /* onTop */));
@@ -835,7 +836,7 @@
 
         // Create a secondary display at bottom.
         final TestDisplayContent secondaryDisplay = addNewDisplayContentAt(POSITION_BOTTOM);
-        final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers;
+        final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea();
         secondaryTaskContainer.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
 
@@ -951,7 +952,7 @@
         final ActivityStarter starter = prepareStarter(0 /* flags */);
         starter.mStartActivity = new ActivityBuilder(mService).build();
         final Task task = new TaskBuilder(mService.mStackSupervisor)
-                .setStack(mService.mRootWindowContainer.getDefaultDisplay().createStack(
+                .setStack(mService.mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */))
                 .setUserId(10)
                 .build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 9240b22..67d4769 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -372,7 +372,7 @@
 
         Task build() {
             if (mStack == null && mCreateStack) {
-                mStack = mSupervisor.mRootWindowContainer.getDefaultDisplay().createStack(
+                mStack = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
                 spyOn(mStack);
             }
@@ -408,6 +408,7 @@
     static class StackBuilder {
         private final RootWindowContainer mRootWindowContainer;
         private DisplayContent mDisplay;
+        private TaskDisplayArea mTaskDisplayArea;
         private int mStackId = -1;
         private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
         private int mActivityType = ACTIVITY_TYPE_STANDARD;
@@ -419,6 +420,7 @@
         StackBuilder(RootWindowContainer root) {
             mRootWindowContainer = root;
             mDisplay = mRootWindowContainer.getDefaultDisplay();
+            mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea();
         }
 
         StackBuilder setWindowingMode(int windowingMode) {
@@ -436,8 +438,20 @@
             return this;
         }
 
+        /**
+         * Set the parent {@link DisplayContent} and use the default task display area. Overrides
+         * the task display area, if was set before.
+         */
         StackBuilder setDisplay(DisplayContent display) {
             mDisplay = display;
+            mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea();
+            return this;
+        }
+
+        /** Set the parent {@link TaskDisplayArea}. Overrides the display, if was set before. */
+        StackBuilder setTaskDisplayArea(TaskDisplayArea taskDisplayArea) {
+            mTaskDisplayArea = taskDisplayArea;
+            mDisplay = mTaskDisplayArea.mDisplayContent;
             return this;
         }
 
@@ -462,9 +476,8 @@
         }
 
         ActivityStack build() {
-            final int stackId = mStackId >= 0 ? mStackId
-                    : mDisplay.mTaskContainers.getNextStackId();
-            final ActivityStack stack = mDisplay.mTaskContainers.createStackUnchecked(
+            final int stackId = mStackId >= 0 ? mStackId : mTaskDisplayArea.getNextStackId();
+            final ActivityStack stack = mTaskDisplayArea.createStackUnchecked(
                     mWindowingMode, mActivityType, stackId, mOnTop, mInfo, mIntent,
                     false /* createdByOrganizer */);
             final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 4cb50c7..9b7ffd6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -147,7 +147,7 @@
         // Reparenting to a display with different windowing mode may trigger
         // a change transition internally, but it should be cleaned-up once
         // the display change is complete.
-        mStack.reparent(mDisplayContent, true);
+        mStack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true);
 
         assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 8b91c7e..8c8fd05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -169,7 +169,7 @@
         assertTrue(dc1.mOpeningApps.size() > 0);
 
         // Move stack to another display.
-        stack1.reparent(dc2, true);
+        stack1.reparent(dc2.getDefaultTaskDisplayArea(), true);
 
         // Verify if token are cleared from both pending transition list in former display.
         assertFalse(dc1.mOpeningApps.contains(activity1));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 38b3d76..a901d1e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -281,7 +281,7 @@
         assertEquals(dc, activity.getDisplayContent());
 
         // Move stack to first display.
-        mDisplayContent.moveStackToDisplay(stack, true /* onTop */);
+        stack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */);
         assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
         assertEquals(mDisplayContent, stack.getDisplayContent());
         assertEquals(mDisplayContent, task.getDisplayContent());
@@ -753,7 +753,7 @@
         doReturn(true).when(freeformStack).isVisible();
         freeformStack.getTopChild().setBounds(100, 100, 300, 400);
 
-        assertTrue(dc.isStackVisible(WINDOWING_MODE_FREEFORM));
+        assertTrue(dc.getDefaultTaskDisplayArea().isStackVisible(WINDOWING_MODE_FREEFORM));
 
         freeformStack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         stack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT);
@@ -1096,8 +1096,7 @@
 
     @Test
     public void testGetOrCreateRootHomeTask_defaultDisplay() {
-        DisplayContent defaultDisplay = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY);
-        TaskDisplayArea defaultTaskDisplayArea = defaultDisplay.mTaskContainers;
+        TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea();
 
         // Remove the current home stack if it exists so a new one can be created below.
         ActivityStack homeTask = defaultTaskDisplayArea.getRootHomeTask();
@@ -1116,7 +1115,7 @@
         doReturn(false).when(display).isUntrustedVirtualDisplay();
 
         // Remove the current home stack if it exists so a new one can be created below.
-        TaskDisplayArea taskDisplayArea = display.mTaskContainers;
+        TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
         ActivityStack homeTask = taskDisplayArea.getRootHomeTask();
         if (homeTask != null) {
             taskDisplayArea.removeChild(homeTask);
@@ -1129,7 +1128,7 @@
     @Test
     public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
         DisplayContent display = createNewDisplay();
-        TaskDisplayArea taskDisplayArea = display.mTaskContainers;
+        TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
         doReturn(false).when(display).supportsSystemDecorations();
 
         assertNull(taskDisplayArea.getRootHomeTask());
@@ -1139,7 +1138,7 @@
     @Test
     public void testGetOrCreateRootHomeTask_untrustedVirtualDisplay() {
         DisplayContent display = createNewDisplay();
-        TaskDisplayArea taskDisplayArea = display.mTaskContainers;
+        TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
         doReturn(true).when(display).isUntrustedVirtualDisplay();
 
         assertNull(taskDisplayArea.getRootHomeTask());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index cf7411e..9b2a2db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -860,6 +860,8 @@
             mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay;
             when(mMockDisplayContent.calculateDisplayCutoutForRotation(anyInt()))
                     .thenReturn(WmDisplayCutout.NO_CUTOUT);
+            when(mMockDisplayContent.getDefaultTaskDisplayArea())
+                    .thenReturn(mock(TaskDisplayArea.class));
 
             mMockDisplayPolicy = mock(DisplayPolicy.class);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index ae467c0..6a71a7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -114,8 +114,8 @@
         when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId)))
                 .thenReturn(mTestDisplay);
 
-        ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE,
-                ACTIVITY_TYPE_STANDARD, /* onTop */ true);
+        ActivityStack stack = mTestDisplay.getDefaultTaskDisplayArea()
+                .createStack(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true);
         mTestTask = new TaskBuilder(mSupervisor).setComponent(TEST_COMPONENT).setStack(stack)
                 .build();
         mTestTask.mUserId = TEST_USER_ID;
@@ -337,8 +337,8 @@
     public void testClearsRecordsOfTheUserOnUserCleanUp() {
         mTarget.saveTask(mTestTask);
 
-        ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE,
-                ACTIVITY_TYPE_STANDARD, /* onTop */ true);
+        ActivityStack stack = mTestDisplay.getDefaultTaskDisplayArea().createStack(
+                TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true);
         final Task anotherTaskOfTheSameUser = new TaskBuilder(mSupervisor)
                 .setComponent(ALTERNATIVE_COMPONENT)
                 .setUserId(TEST_USER_ID)
@@ -349,7 +349,7 @@
         anotherTaskOfTheSameUser.setHasBeenVisible(true);
         mTarget.saveTask(anotherTaskOfTheSameUser);
 
-        stack = mTestDisplay.createStack(TEST_WINDOWING_MODE,
+        stack = mTestDisplay.getDefaultTaskDisplayArea().createStack(TEST_WINDOWING_MODE,
                 ACTIVITY_TYPE_STANDARD, /* onTop */ true);
         final Task anotherTaskOfDifferentUser = new TaskBuilder(mSupervisor)
                 .setComponent(TEST_COMPONENT)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 071386f..d9c3ace 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -28,7 +28,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -119,7 +118,7 @@
     public void setUp() throws Exception {
         mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
         spyOn(mTaskPersister);
-        mTaskContainer = mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).mTaskContainers;
+        mTaskContainer = mRootWindowContainer.getDefaultTaskDisplayArea();
 
         // Set the recent tasks we should use for testing in this class.
         mRecentTasks = new TestRecentTasks(mService, mTaskPersister);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index da07bac..6d2b7b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -105,7 +105,7 @@
         mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
         mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
                 DEFAULT_DISPLAY));
-        mRootHomeTask = mDefaultDisplay.getRootHomeTask();
+        mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
         assertNotNull(mRootHomeTask);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 6810f64..881561f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -88,7 +88,7 @@
 
     @Test
     public void testRecentsActivityVisiblility() {
-        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers;
+        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         ActivityStack recentsStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
         ActivityRecord recentActivity = new ActivityBuilder(mService)
@@ -116,8 +116,7 @@
 
     @Test
     public void testPreloadRecentsActivity() {
-        TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultDisplay()
-                .mTaskContainers;
+        TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         final ActivityStack homeStack =
                 defaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
         defaultTaskDisplayArea.positionStackAtTop(homeStack, false /* includingParents */);
@@ -178,8 +177,7 @@
     @Test
     public void testRestartRecentsActivity() throws Exception {
         // Have a recents activity that is not attached to its process (ActivityRecord.app = null).
-        TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultDisplay()
-                .mTaskContainers;
+        TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         ActivityStack recentsStack = defaultTaskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
         ActivityRecord recentActivity = new ActivityBuilder(mService).setComponent(
@@ -208,7 +206,7 @@
 
     @Test
     public void testSetLaunchTaskBehindOfTargetActivity() {
-        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers;
+        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         ActivityStack homeStack = taskDisplayArea.getRootHomeTask();
         // Assume the home activity support recents.
         ActivityRecord targetActivity = homeStack.getTopNonFinishingActivity();
@@ -253,7 +251,7 @@
 
     @Test
     public void testCancelAnimationOnVisibleStackOrderChange() {
-        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers;
+        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         ActivityStack fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         new ActivityBuilder(mService)
@@ -298,7 +296,7 @@
 
     @Test
     public void testKeepAnimationOnHiddenStackOrderChange() {
-        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers;
+        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         ActivityStack fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         new ActivityBuilder(mService)
@@ -334,7 +332,8 @@
 
     @Test
     public void testMultipleUserHomeActivity_findUserHomeTask() {
-        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers;
+        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay()
+                .getDefaultTaskDisplayArea();
         ActivityStack homeStack = taskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED,
                 ACTIVITY_TYPE_HOME);
         ActivityRecord otherUserHomeActivity = new ActivityBuilder(mService)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 83631049..48d4e70 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -89,7 +89,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mFullscreenStack = mRootWindowContainer.getDefaultDisplay().createStack(
+        mFullscreenStack = mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         doNothing().when(mService).updateSleepIfNeededLocked();
     }
@@ -129,8 +129,8 @@
         mRootWindowContainer.moveActivityToPinnedStack(firstActivity, sourceBounds,
                 0f /*aspectRatio*/, "initialMove");
 
-        final DisplayContent display = mFullscreenStack.getDisplay();
-        ActivityStack pinnedStack = display.getRootPinnedTask();
+        final TaskDisplayArea taskDisplayArea = mFullscreenStack.getDisplayArea();
+        ActivityStack pinnedStack = taskDisplayArea.getRootPinnedTask();
         // Ensure a task has moved over.
         ensureStackPlacement(pinnedStack, firstActivity);
         ensureStackPlacement(mFullscreenStack, secondActivity);
@@ -140,8 +140,9 @@
                 0f /*aspectRatio*/, "secondMove");
 
         // Need to get stacks again as a new instance might have been created.
-        pinnedStack = display.getRootPinnedTask();
-        mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        pinnedStack = taskDisplayArea.getRootPinnedTask();
+        mFullscreenStack = taskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD);
         // Ensure stacks have swapped tasks.
         ensureStackPlacement(pinnedStack, secondActivity);
         ensureStackPlacement(mFullscreenStack, firstActivity);
@@ -215,6 +216,8 @@
 
         doReturn(isFocusedStack).when(stack).isFocusedStackOnDisplay();
         doReturn(isFocusedStack ? stack : null).when(display).getFocusedStack();
+        TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea();
+        doReturn(isFocusedStack ? stack : null).when(defaultTaskDisplayArea).getFocusedStack();
         mRootWindowContainer.applySleepTokens(true);
         verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked();
         verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked(
@@ -226,26 +229,29 @@
      */
     @Test
     public void testRemovingStackOnAppCrash() {
-        final DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay();
-        final int originalStackCount = defaultDisplay.getStackCount();
-        final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack(
+        final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+                .getDefaultTaskDisplayArea();
+        final int originalStackCount = defaultTaskDisplayArea.getStackCount();
+        final ActivityStack stack = defaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
         final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
                 .setStack(stack).build();
 
-        assertEquals(originalStackCount + 1, defaultDisplay.getStackCount());
+        assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getStackCount());
 
         // Let's pretend that the app has crashed.
         firstActivity.app.setThread(null);
         mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test");
 
         // Verify that the stack was removed.
-        assertEquals(originalStackCount, defaultDisplay.getStackCount());
+        assertEquals(originalStackCount, defaultTaskDisplayArea.getStackCount());
     }
 
     @Test
     public void testFocusability() {
-        final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack(
+        final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+                .getDefaultTaskDisplayArea();
+        final ActivityStack stack = defaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
                 .setStack(stack).build();
@@ -259,7 +265,7 @@
         assertFalse(stack.isTopActivityFocusable());
         assertFalse(activity.isFocusable());
 
-        final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
+        final ActivityStack pinnedStack = defaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true)
                 .setStack(pinnedStack).build();
@@ -288,7 +294,7 @@
     @Test
     public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() {
         // Create primary split-screen stack with a task and an activity.
-        final ActivityStack primaryStack = mRootWindowContainer.getDefaultDisplay()
+        final ActivityStack primaryStack = mRootWindowContainer.getDefaultTaskDisplayArea()
                 .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
                         true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(primaryStack).build();
@@ -311,7 +317,6 @@
     @Test
     public void testFindTaskToMoveToFrontWhenRecentsOnTop() {
         // Create stack/task on default display.
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
         final ActivityStack targetStack = new StackBuilder(mRootWindowContainer)
                 .setOnTop(false)
                 .build();
@@ -325,7 +330,7 @@
         mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
                 false);
 
-        final TaskDisplayArea taskDisplayArea = display.mTaskContainers;
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         verify(taskDisplayArea).moveHomeStackToFront(contains(reason));
     }
 
@@ -336,8 +341,8 @@
     @Test
     public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() {
         // Create stack/task on default display.
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-        final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        final ActivityStack targetStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */);
         final Task targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build();
 
@@ -353,7 +358,6 @@
         mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
                 false);
 
-        final TaskDisplayArea taskDisplayArea = display.mTaskContainers;
         verify(taskDisplayArea, never()).moveHomeStackToFront(contains(reason));
     }
 
@@ -364,12 +368,12 @@
     @Test
     public void testResumeActivityWhenNonTopmostStackIsTopFocused() {
         // Create a stack at bottom.
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-        final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
         final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
-        display.mTaskContainers.positionStackAtBottom(targetStack);
+        taskDisplayArea.positionStackAtBottom(targetStack);
 
         // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it
         // is the current top focused stack.
@@ -392,10 +396,9 @@
     @Test
     public void testResumeFocusedStacksStartsHomeActivity_NoActivities() {
         mFullscreenStack.removeIfPossible();
-        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask()
-                .removeIfPossible();
-        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY)
-                .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        taskDisplayArea.getRootHomeTask().removeIfPossible();
+        taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
 
         doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), anyInt());
 
@@ -415,16 +418,15 @@
     @Test
     public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() {
         mFullscreenStack.removeIfPossible();
-        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask()
-                .removeIfPossible();
-        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY)
-                .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        taskDisplayArea.getRootHomeTask().removeIfPossible();
+        taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
 
         // Create an activity on secondary display.
         final TestDisplayContent secondDisplay = addNewDisplayContentAt(
                 DisplayContent.POSITION_TOP);
-        final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityStack stack = secondDisplay.getDefaultTaskDisplayArea()
+                .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
         new ActivityBuilder(mService).setTask(task).build();
 
@@ -446,8 +448,8 @@
     @Test
     public void testResumeActivityLingeringTransition() {
         // Create a stack at top.
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-        final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
         final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
@@ -466,13 +468,13 @@
     @Test
     public void testResumeActivityLingeringTransition_notExecuted() {
         // Create a stack at bottom.
-        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-        final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
         final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
         activity.setState(ActivityState.RESUMED, "test");
-        display.mTaskContainers.positionStackAtBottom(targetStack);
+        taskDisplayArea.positionStackAtBottom(targetStack);
 
         // Assume the stack is at the topmost position
         assertFalse(targetStack.isTopStackInDisplayArea());
@@ -809,20 +811,20 @@
     public void testSwitchUser_missingHomeRootTask() {
         doReturn(mFullscreenStack).when(mRootWindowContainer).getTopDisplayFocusedStack();
 
-        DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay();
-        ActivityStack homeStack = defaultDisplay.getRootHomeTask();
+        final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+        ActivityStack homeStack = taskDisplayArea.getRootHomeTask();
         if (homeStack != null) {
             homeStack.removeImmediately();
         }
-        assertNull(defaultDisplay.getRootHomeTask());
+        assertNull(taskDisplayArea.getRootHomeTask());
 
         int currentUser = mRootWindowContainer.mCurrentUser;
         int otherUser = currentUser + 1;
 
         mRootWindowContainer.switchUser(otherUser, null);
 
-        assertNotNull(defaultDisplay.getRootHomeTask());
-        assertEquals(defaultDisplay.getTopStack(), defaultDisplay.getRootHomeTask());
+        assertNotNull(taskDisplayArea.getRootHomeTask());
+        assertEquals(taskDisplayArea.getTopStack(), taskDisplayArea.getRootHomeTask());
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index d6a67ab..3d3a0f1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -72,7 +72,8 @@
         final int numTasks = 10;
         int activeTime = 0;
         for (int i = 0; i < numTasks; i++) {
-            createTask(display.getStackAt(i % numStacks), ".Task" + i, i, activeTime++, null);
+            createTask(display.getDefaultTaskDisplayArea().getStackAt(i % numStacks),
+                    ".Task" + i, i, activeTime++, null);
         }
 
         // Ensure that the latest tasks were returned in order of decreasing last active time,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 893a145..6734694 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -189,7 +189,7 @@
         final int originalDpi = mActivity.getConfiguration().densityDpi;
 
         // Move the non-resizable activity to the new display.
-        mStack.reparent(newDisplay.mDisplayContent, true /* onTop */);
+        mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
 
         assertEquals(originalBounds.width(), mActivity.getBounds().width());
         assertEquals(originalBounds.height(), mActivity.getBounds().height());
@@ -257,7 +257,7 @@
                 .setCanRotate(false).setNotch(notchHeight).build();
 
         // Move the non-resizable activity to the new display.
-        mStack.reparent(newDisplay, true /* onTop */);
+        mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
         // The configuration bounds should keep the same.
         assertEquals(origWidth, configBounds.width());
         assertEquals(origHeight, configBounds.height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index af76e7f..af3ec38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -316,7 +316,7 @@
         // that the default display is in fullscreen mode.
         display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
         spyOn(display);
-        final TaskDisplayArea taskDisplayArea = display.mTaskContainers;
+        final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
         spyOn(taskDisplayArea);
         final ActivityStack homeStack = taskDisplayArea.getStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index a3446d1..1a38ff2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -1312,14 +1312,14 @@
     }
 
     private ActivityRecord createSourceActivity(TestDisplayContent display) {
-        final ActivityStack stack = display.createStack(display.getWindowingMode(),
-                ACTIVITY_TYPE_STANDARD, true);
+        final ActivityStack stack = display.getDefaultTaskDisplayArea()
+                .createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
         return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build();
     }
 
     private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) {
-        final ActivityStack stack = display.createStack(display.getWindowingMode(),
-                ACTIVITY_TYPE_STANDARD, true);
+        final ActivityStack stack = display.getDefaultTaskDisplayArea()
+                .createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
         stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
         // Just work around the unnecessary adjustments for bounds.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index f76809b..50584c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -203,9 +203,9 @@
     @Test
     public void testFitWithinBounds() {
         final Rect parentBounds = new Rect(10, 10, 200, 200);
-        DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
-        ActivityStack stack = display.createStack(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
-                true /* onTop */);
+        TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+        ActivityStack stack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
         Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
         final Configuration parentConfig = stack.getConfiguration();
         parentConfig.windowConfiguration.setBounds(parentBounds);
@@ -438,9 +438,9 @@
 
     @Test
     public void testInsetDisregardedWhenFreeformOverlapsNavBar() {
-        DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
-        ActivityStack stack = display.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
-                true /* onTop */);
+        TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+        ActivityStack stack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
         DisplayInfo displayInfo = new DisplayInfo();
         mService.mContext.getDisplay().getDisplayInfo(displayInfo);
         final int displayHeight = displayInfo.logicalHeight;
@@ -959,8 +959,8 @@
     private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
             Rect expectedConfigBounds) {
 
-        DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
-        ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD,
+        TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+        ActivityStack stack = taskDisplayArea.createStack(windowingMode, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 6387a3b..d48e827 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -171,7 +171,7 @@
 
         // Reparent
         clearInvocations(task1); // reset the number of onDisplayChanged for task.
-        stack1.reparent(dc, true /* onTop */);
+        stack1.reparent(dc.getDefaultTaskDisplayArea(), true /* onTop */);
         assertEquals(dc, stack1.getDisplayContent());
         final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
         final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 900f014..a4f1487 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -254,7 +254,7 @@
 
     private WindowState createWallpaperTargetWindow(DisplayContent dc) {
         final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
-                .setStack(dc.getRootHomeTask())
+                .setStack(dc.getDefaultTaskDisplayArea().getRootHomeTask())
                 .setCreateTask(true)
                 .build();
         homeActivity.setVisibility(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 27ea37d..118c2e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -780,8 +780,8 @@
                 WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
 
         final DisplayContent newDc = createNewDisplay();
-        mDisplayContent.removeStack(stack);
-        newDc.mTaskContainers.addChild(stack, POSITION_TOP);
+        stack.getDisplayArea().removeStack(stack);
+        newDc.getDefaultTaskDisplayArea().addChild(stack, POSITION_TOP);
 
         verify(stack).onDisplayChanged(newDc);
         verify(task).onDisplayChanged(newDc);
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index c37735c..13a8273 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -153,8 +153,10 @@
                 cid.bands.stream().mapToInt(Integer::intValue).toArray(), cid.base.bandwidth,
                 cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong,
                 cid.base.operatorNames.alphaShort, cid.additionalPlmns,
-                cid.optionalCsgInfo.csgInfo() != null
-                        ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null);
+                cid.optionalCsgInfo.getDiscriminator()
+                        == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo
+                                ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo())
+                                        : null);
     }
 
     private CellIdentityLte(@NonNull CellIdentityLte cid) {
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 3f95596..6dffe92 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -128,8 +128,11 @@
         this(cid.base.base.mcc, cid.base.base.mnc, cid.base.base.lac, cid.base.base.cid,
                 cid.base.base.cpid, cid.base.uarfcn, cid.base.operatorNames.alphaLong,
                 cid.base.operatorNames.alphaShort,
-                cid.additionalPlmns, cid.optionalCsgInfo.csgInfo() != null
-                        ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null);
+                cid.additionalPlmns,
+                cid.optionalCsgInfo.getDiscriminator()
+                        == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo
+                                ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo())
+                                        : null);
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 38c4ed4..eab174a 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -123,8 +123,10 @@
         this(cid.base.base.lac, cid.base.base.cid, cid.base.base.psc, cid.base.base.uarfcn,
                 cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong,
                 cid.base.operatorNames.alphaShort, cid.additionalPlmns,
-                cid.optionalCsgInfo.csgInfo() != null
-                        ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null);
+                cid.optionalCsgInfo.getDiscriminator()
+                        == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo
+                                ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo())
+                                        : null);
     }
 
     private CellIdentityWcdma(@NonNull CellIdentityWcdma cid) {
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 8de27e8..0fc9be3 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.net.RouteInfo.RTN_UNREACHABLE;
+
 import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
 import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
 import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
@@ -46,6 +48,7 @@
 import org.junit.runner.RunWith;
 
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1257,4 +1260,26 @@
         final LinkProperties Ipv6 = makeIpv6LinkProperties();
         assertTrue(Ipv6.hasIpv6DnsServer());
     }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testHasIpv4UnreachableDefaultRoute() {
+        final LinkProperties lp = makeTestObject();
+        assertFalse(lp.hasIpv4UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv6UnreachableDefaultRoute());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
+        assertTrue(lp.hasIpv4UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv6UnreachableDefaultRoute());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testHasIpv6UnreachableDefaultRoute() {
+        final LinkProperties lp = makeTestObject();
+        assertFalse(lp.hasIpv6UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv4UnreachableDefaultRoute());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
+        assertTrue(lp.hasIpv6UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv4UnreachableDefaultRoute());
+    }
 }
diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
index 1658262..8204b49 100644
--- a/tests/net/common/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -31,6 +31,7 @@
 
 import android.os.Build;
 
+import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -62,6 +63,11 @@
         return new IpPrefix(prefix);
     }
 
+    private static boolean isAtLeastR() {
+        // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R)
+        return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR();
+    }
+
     @Test
     public void testConstructor() {
         RouteInfo r;
@@ -195,78 +201,130 @@
         assertTrue(r.isDefaultRoute());
         assertTrue(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
         assertFalse(r.isHostRoute());
         assertTrue(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertTrue(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE);
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertTrue(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE);
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertTrue(r.isIPv6UnreachableDefault());
+        }
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b02398d..912a27f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -6336,6 +6336,7 @@
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
         final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
@@ -6361,6 +6362,7 @@
     public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
@@ -6392,6 +6394,7 @@
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
         final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
@@ -6428,6 +6431,7 @@
         reset(mMockNetd);
         lp = new LinkProperties();
         lp.setInterfaceName("tun1");
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         vpnNetworkAgent.sendLinkProperties(lp);
         waitForIdle();
@@ -6440,6 +6444,7 @@
     public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final UidRange vpnRange = UidRange.createForUser(VPN_USER);
diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
index de2f5d9..6c2e6dd 100644
--- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -42,7 +42,7 @@
     public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0;
 
     /**
-     * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode).
+     * Easy Connect R2 Success event: Configuration applied by Enrollee (Configurator mode).
      * This is the last and final Easy Connect event when both the local device and remote device
      * implement R2. If either the local device or remote device implement R1, this event will never
      * be received, and the {@link #EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received.
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index dfeea9f..f1be8b2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1887,12 +1887,20 @@
      * <li> If user reset network settings, all added suggestions will be discarded. Apps can use
      * {@link #getNetworkSuggestions()} to check if their suggestions are in the device.</li>
      * <li> In-place modification of existing suggestions are allowed.
-     * If the provided suggestions {@link WifiNetworkSuggestion#equals(Object)} any previously
-     * provided suggestions by the app. Previous suggestions will be updated</li>
+     * <li>If the provided suggestions includes any previously provided suggestions by the app,
+     * previous suggestions will be updated.</li>
+     * <li>If one of the provided suggestions marks a previously unmetered suggestion as metered and
+     * the device is currently connected to that suggested network, then the device will disconnect
+     * from that network. The system will immediately re-evaluate all the network candidates
+     * and possibly reconnect back to the same suggestion. This disconnect is to make sure that any
+     * traffic flowing over unmetered networks isn't accidentally continued over a metered network.
+     * </li>
+     * </li>
      *
      * @param networkSuggestions List of network suggestions provided by the app.
      * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
      * @throws {@link SecurityException} if the caller is missing required permissions.
+     * @see WifiNetworkSuggestion#equals(Object)
      */
     @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
     public @NetworkSuggestionsStatusCode int addNetworkSuggestions(
@@ -4152,6 +4160,10 @@
      *
      * This function is used instead of a enableNetwork() and reconnect()
      *
+     * <li> This API will cause reconnect if the credentials of the current active
+     * connection has been changed.</li>
+     * <li> This API will cause reconnect if the current active connection is marked metered.</li>
+     *
      * @param networkId the ID of the network as returned by {@link #addNetwork} or {@link
      *        getConfiguredNetworks}.
      * @param listener for callbacks on success or failure. Can be null.
@@ -4180,8 +4192,9 @@
      *
      * For an existing network, it accomplishes the task of updateNetwork()
      *
-     * This API will cause reconnect if the crecdentials of the current active
-     * connection has been changed.
+     * <li> This API will cause reconnect if the credentials of the current active
+     * connection has been changed.</li>
+     * <li> This API will cause disconnect if the current active connection is marked metered.</li>
      *
      * @param config the set of variables that describe the configuration,
      *            contained in a {@link WifiConfiguration} object.
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 4c524f4..cedf9b0 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -543,7 +543,7 @@
             wifiConfiguration.priority = mPriority;
             wifiConfiguration.meteredOverride =
                     mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
-                            : WifiConfiguration.METERED_OVERRIDE_NONE;
+                            : WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
             wifiConfiguration.carrierId = mCarrierId;
             wifiConfiguration.trusted = !mIsNetworkUntrusted;
             return wifiConfiguration;
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index ac2f6b2..aca1909 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -56,7 +56,7 @@
                 .get(WifiConfiguration.KeyMgmt.NONE));
         assertTrue(suggestion.isAppInteractionRequired);
         assertFalse(suggestion.isUserInteractionRequired);
-        assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
+        assertEquals(WifiConfiguration.METERED_OVERRIDE_NOT_METERED,
                 suggestion.wifiConfiguration.meteredOverride);
         assertEquals(-1, suggestion.wifiConfiguration.priority);
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
@@ -86,7 +86,7 @@
                 suggestion.wifiConfiguration.preSharedKey);
         assertTrue(suggestion.isAppInteractionRequired);
         assertFalse(suggestion.isUserInteractionRequired);
-        assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
+        assertEquals(WifiConfiguration.METERED_OVERRIDE_NOT_METERED,
                 suggestion.wifiConfiguration.meteredOverride);
         assertEquals(0, suggestion.wifiConfiguration.priority);
         assertFalse(suggestion.isUserAllowedToManuallyConnect);