Merge "Clean up unused LogEvent constructors" into rvc-dev
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index edf25ae..e533b7a 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -499,20 +499,58 @@
             })
     public @interface ParserName {}
 
+    /** Parser name returned by {@link #getParserName()} when no parser has been selected yet. */
     public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN";
+    /**
+     * Parser for the Matroska container format, as defined in the <a
+     * href="https://matroska.org/technical/specs/">spec</a>.
+     */
     public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser";
+    /**
+     * Parser for fragmented files using the MP4 container format, as defined in ISO/IEC 14496-12.
+     */
     public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser";
+    /**
+     * Parser for non-fragmented files using the MP4 container format, as defined in ISO/IEC
+     * 14496-12.
+     */
     public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser";
+    /** Parser for the MP3 container format, as defined in ISO/IEC 11172-3. */
     public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser";
+    /** Parser for the ADTS container format, as defined in ISO/IEC 13818-7. */
     public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser";
+    /**
+     * Parser for the AC-3 container format, as defined in Digital Audio Compression Standard
+     * (AC-3).
+     */
     public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser";
+    /** Parser for the TS container format, as defined in ISO/IEC 13818-1. */
     public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser";
+    /**
+     * Parser for the FLV container format, as defined in Adobe Flash Video File Format
+     * Specification.
+     */
     public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser";
+    /** Parser for the OGG container format, as defined in RFC 3533. */
     public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser";
+    /** Parser for the PS container format, as defined in ISO/IEC 11172-1. */
     public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser";
+    /**
+     * Parser for the WAV container format, as defined in Multimedia Programming Interface and Data
+     * Specifications.
+     */
     public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser";
+    /** Parser for the AMR container format, as defined in RFC 4867. */
     public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser";
+    /**
+     * Parser for the AC-4 container format, as defined by Dolby AC-4: Audio delivery for
+     * Next-Generation Entertainment Services.
+     */
     public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser";
+    /**
+     * Parser for the FLAC container format, as defined in the <a
+     * href="https://xiph.org/flac/">spec</a>.
+     */
     public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser";
 
     // MediaParser parameters.
diff --git a/api/current.txt b/api/current.txt
index 1c1d142..b4db1f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6833,13 +6833,6 @@
     method public final android.os.IBinder onBind(android.content.Intent);
   }
 
-  public class DevicePolicyKeyguardService extends android.app.Service {
-    ctor public DevicePolicyKeyguardService();
-    method @Nullable public void dismiss();
-    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder);
-  }
-
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
@@ -7055,7 +7048,6 @@
     method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
     method public void setRestrictionsProvider(@NonNull android.content.ComponentName, @Nullable android.content.ComponentName);
     method public void setScreenCaptureDisabled(@NonNull android.content.ComponentName, boolean);
-    method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setSecureSetting(@NonNull android.content.ComponentName, String, String);
     method public void setSecurityLoggingEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setShortSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -7082,7 +7074,6 @@
     field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
     field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
-    field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
     field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE";
     field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
     field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
@@ -30284,6 +30275,7 @@
     field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
     field public static final int NET_CAPABILITY_RCS = 8; // 0x8
     field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
+    field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
     field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
     field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
     field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
diff --git a/api/system-current.txt b/api/system-current.txt
index c664bed..c5d319c 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -848,6 +848,13 @@
 
 package android.app.admin {
 
+  public class DevicePolicyKeyguardService extends android.app.Service {
+    ctor public DevicePolicyKeyguardService();
+    method @Nullable public void dismiss();
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder);
+  }
+
   public class DevicePolicyManager {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
@@ -872,8 +879,10 @@
     method @Deprecated @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
+    method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
+    field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
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/hid/README.md b/cmds/hid/README.md
index 7e22d08..620336f 100644
--- a/cmds/hid/README.md
+++ b/cmds/hid/README.md
@@ -38,17 +38,21 @@
 Register a new uhid device
 
 | Field         | Type          | Description                |
-|:-------------:|:-------------:|:--------------------------|
+|:-------------:|:-------------:|:-------------------------- |
 | id            | integer       | Device id                  |
 | command       | string        | Must be set to "register"  |
 | name          | string        | Device name                |
 | vid           | 16-bit integer| Vendor id                  |
 | pid           | 16-bit integer| Product id                 |
+| bus           | string        | Bus that device should use |
 | descriptor    | byte array    | USB HID report descriptor  |
 
 Device ID is used for matching the subsequent commands to a specific device
 to avoid ambiguity when multiple devices are registered.
 
+Device bus is used to determine how the uhid device is connected to the host.
+The options are "usb" and "bluetooth".
+
 USB HID report descriptor should be generated according the the USB HID spec
 and can be checked by reverse parsing using a variety of tools, for example
 [usbdescreqparser][5].
@@ -61,6 +65,7 @@
   "name": "Odie (Test)",
   "vid": 0x18d1,
   "pid": 0x2c40,
+  "bus": "usb",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
     0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
     0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
@@ -142,4 +147,4 @@
 [3]: ../../../../cts/tests/tests/hardware/res/raw/
 [4]: https://developer.android.com/training/game-controllers/controller-input.html#button
 [5]: http://eleccelerator.com/usbdescreqparser/
-[6]: https://developer.android.com/training/game-controllers/controller-input.html
\ No newline at end of file
+[6]: https://developer.android.com/training/game-controllers/controller-input.html
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index a321460..d3d7e1d 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -56,7 +56,6 @@
         "src/condition/condition_util.cpp",
         "src/condition/ConditionWizard.cpp",
         "src/condition/SimpleConditionTracker.cpp",
-        "src/condition/StateConditionTracker.cpp",
         "src/config/ConfigKey.cpp",
         "src/config/ConfigListener.cpp",
         "src/config/ConfigManager.cpp",
@@ -315,7 +314,6 @@
         "tests/condition/CombinationConditionTracker_test.cpp",
         "tests/condition/ConditionTimer_test.cpp",
         "tests/condition/SimpleConditionTracker_test.cpp",
-        "tests/condition/StateConditionTracker_test.cpp",
         "tests/ConfigManager_test.cpp",
         "tests/e2e/Alarm_e2e_test.cpp",
         "tests/e2e/Anomaly_count_e2e_test.cpp",
@@ -337,6 +335,7 @@
         "tests/external/StatsPullerManager_test.cpp",
         "tests/FieldValue_test.cpp",
         "tests/guardrail/StatsdStats_test.cpp",
+        "tests/HashableDimensionKey_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
         "tests/log_event/LogEventQueue_test.cpp",
         "tests/LogEntryMatcher_test.cpp",
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 23d8f59..29249f4 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -230,6 +230,47 @@
     }
 }
 
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const vector<Metric2State>& stateLinks, const int32_t stateAtomId) {
+    if (whatKey.getValues().size() < primaryKey.getValues().size()) {
+        ALOGE("Contains linked values false: whatKey is too small");
+        return false;
+    }
+
+    for (const auto& primaryValue : primaryKey.getValues()) {
+        bool found = false;
+        for (const auto& whatValue : whatKey.getValues()) {
+            if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) &&
+                primaryValue.mValue == whatValue.mValue) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField) {
+    for (auto stateLink : stateLinks) {
+        if (stateLink.stateAtomId != stateAtomId) {
+            continue;
+        }
+
+        for (size_t i = 0; i < stateLink.stateFields.size(); i++) {
+            if (stateLink.stateFields[i].mMatcher == stateField &&
+                stateLink.metricFields[i].mMatcher == metricField) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
     if (s1.size() != s2.size()) {
         return s1.size() < s2.size();
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index a766bba..33a5024 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -110,6 +110,10 @@
         return mStateValuesKey;
     }
 
+    inline HashableDimensionKey* getMutableStateValuesKey() {
+        return &mStateValuesKey;
+    }
+
     inline void setStateValuesKey(const HashableDimensionKey& key) {
         mStateValuesKey = key;
     }
@@ -169,6 +173,32 @@
 void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
                           HashableDimensionKey* statePrimaryKey);
 
+/**
+ * Returns true if the primaryKey values are a subset of the whatKey values.
+ * The values from the primaryKey come from the state atom, so we need to
+ * check that a link exists between the state atom field and what atom field.
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 27, {uid: 1005}]
+ * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}]
+ * Returns false
+ */
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const std::vector<Metric2State>& stateLinks,
+                               const int32_t stateAtomId);
+
+/**
+ * Returns true if there is a Metric2State link that links the stateField and
+ * the metricField (they are equal fields from different atoms).
+ */
+bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 14585c3..97512ed 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -334,6 +334,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 57d4d78..5cd00c3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -123,7 +123,8 @@
         BatteryLevelChanged battery_level_changed =
                 30 [(module) = "framework", (module) = "statsdtest"];
         ChargingStateChanged charging_state_changed = 31 [(module) = "framework"];
-        PluggedStateChanged plugged_state_changed = 32 [(module) = "framework"];
+        PluggedStateChanged plugged_state_changed = 32
+                [(module) = "framework", (module) = "statsdtest"];
         InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"];
         TouchEventReported touch_event_reported = 34;
         WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"];
diff --git a/cmds/statsd/src/condition/StateConditionTracker.cpp b/cmds/statsd/src/condition/StateConditionTracker.cpp
deleted file mode 100644
index d19a176..0000000
--- a/cmds/statsd/src/condition/StateConditionTracker.cpp
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include "StateConditionTracker.h"
-#include "guardrail/StatsdStats.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::vector;
-
-StateConditionTracker::StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
-                           const SimplePredicate& simplePredicate,
-                           const unordered_map<int64_t, int>& trackerNameIndexMap,
-                           const vector<Matcher> primaryKeys)
-    : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) {
-    if (simplePredicate.has_start()) {
-        auto pair = trackerNameIndexMap.find(simplePredicate.start());
-        if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
-            return;
-        }
-        mStartLogMatcherIndex = pair->second;
-        mTrackerIndex.insert(mStartLogMatcherIndex);
-    } else {
-        ALOGW("Condition %lld must have a start matcher", (long long)id);
-        return;
-    }
-
-    if (simplePredicate.has_dimensions()) {
-        translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
-        if (mOutputDimensions.size() > 0) {
-            mSliced = true;
-            mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
-        } else {
-            ALOGW("Condition %lld has invalid dimensions", (long long)id);
-            return;
-        }
-    } else {
-        ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id);
-        return;
-    }
-
-    if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
-        mInitialValue = ConditionState::kFalse;
-    } else {
-        mInitialValue = ConditionState::kUnknown;
-    }
-
-    mNonSlicedConditionState = mInitialValue;
-    mInitialized = true;
-}
-
-StateConditionTracker::~StateConditionTracker() {
-    VLOG("~StateConditionTracker()");
-}
-
-bool StateConditionTracker::init(const vector<Predicate>& allConditionConfig,
-                        const vector<sp<ConditionTracker>>& allConditionTrackers,
-                        const unordered_map<int64_t, int>& conditionIdIndexMap,
-                        vector<bool>& stack) {
-    return mInitialized;
-}
-
-void StateConditionTracker::dumpState() {
-    VLOG("StateConditionTracker %lld DUMP:", (long long)mConditionId);
-    for (const auto& value : mSlicedState) {
-        VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str());
-    }
-    VLOG("Last Changed to True: ");
-    for (const auto& value : mLastChangedToTrueDimensions) {
-        VLOG("%s", value.toString().c_str());
-    }
-    VLOG("Last Changed to False: ");
-    for (const auto& value : mLastChangedToFalseDimensions) {
-        VLOG("%s", value.toString().c_str());
-    }
-}
-
-bool StateConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
-    if (mSlicedState.find(newKey) != mSlicedState.end()) {
-        // if the condition is not sliced or the key is not new, we are good!
-        return false;
-    }
-    // 1. Report the tuple count if the tuple count > soft limit
-    if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
-        size_t newTupleCount = mSlicedState.size() + 1;
-        StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
-        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("Predicate %lld dropping data for dimension key %s",
-                (long long)mConditionId, newKey.toString().c_str());
-            return true;
-        }
-    }
-    return false;
-}
-
-void StateConditionTracker::evaluateCondition(const LogEvent& event,
-                                     const vector<MatchingState>& eventMatcherValues,
-                                     const vector<sp<ConditionTracker>>& mAllConditions,
-                                     vector<ConditionState>& conditionCache,
-                                     vector<bool>& conditionChangedCache) {
-    mLastChangedToTrueDimensions.clear();
-    mLastChangedToFalseDimensions.clear();
-    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        // it has been evaluated.
-        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
-        return;
-    }
-
-    if (mStartLogMatcherIndex >= 0 &&
-        eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) {
-        conditionCache[mIndex] =
-                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-        conditionChangedCache[mIndex] = false;
-        return;
-    }
-
-    VLOG("StateConditionTracker evaluate event %s", event.ToString().c_str());
-
-    // Primary key can exclusive fields must be simple fields. so there won't be more than
-    // one keys matched.
-    HashableDimensionKey primaryKey;
-    HashableDimensionKey state;
-    if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) ||
-        !filterValues(mOutputDimensions, event.getValues(), &state)) {
-        ALOGE("Failed to filter fields in the event?? panic now!");
-        conditionCache[mIndex] =
-                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-        conditionChangedCache[mIndex] = false;
-        return;
-    }
-    hitGuardRail(primaryKey);
-
-    VLOG("StateConditionTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
-
-    auto it = mSlicedState.find(primaryKey);
-    if (it == mSlicedState.end()) {
-        mSlicedState[primaryKey] = state;
-        conditionCache[mIndex] = ConditionState::kTrue;
-        mLastChangedToTrueDimensions.insert(state);
-        conditionChangedCache[mIndex] = true;
-    } else if (!(it->second == state)) {
-        mLastChangedToFalseDimensions.insert(it->second);
-        mLastChangedToTrueDimensions.insert(state);
-        mSlicedState[primaryKey] = state;
-        conditionCache[mIndex] = ConditionState::kTrue;
-        conditionChangedCache[mIndex] = true;
-    } else {
-        conditionCache[mIndex] = ConditionState::kTrue;
-        conditionChangedCache[mIndex] = false;
-    }
-
-    if (DEBUG) {
-        dumpState();
-    }
-    return;
-}
-
-void StateConditionTracker::isConditionMet(
-        const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
-        const bool isPartialLink,
-        vector<ConditionState>& conditionCache) const {
-    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        // it has been evaluated.
-        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
-        return;
-    }
-
-    const auto pair = conditionParameters.find(mConditionId);
-    if (pair == conditionParameters.end()) {
-        if (mSlicedState.size() > 0) {
-            conditionCache[mIndex] = ConditionState::kTrue;
-        } else {
-            conditionCache[mIndex] = ConditionState::kUnknown;
-        }
-        return;
-    }
-
-    const auto& primaryKey = pair->second;
-    conditionCache[mIndex] = mInitialValue;
-    auto it = mSlicedState.find(primaryKey);
-    if (it != mSlicedState.end()) {
-        conditionCache[mIndex] = ConditionState::kTrue;
-    }
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/condition/StateConditionTracker.h b/cmds/statsd/src/condition/StateConditionTracker.h
deleted file mode 100644
index 0efe1fb..0000000
--- a/cmds/statsd/src/condition/StateConditionTracker.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <gtest/gtest_prod.h>
-#include "ConditionTracker.h"
-#include "config/ConfigKey.h"
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "stats_util.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class StateConditionTracker : public virtual ConditionTracker {
-public:
-    StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
-                 const SimplePredicate& simplePredicate,
-                 const std::unordered_map<int64_t, int>& trackerNameIndexMap,
-                 const vector<Matcher> primaryKeys);
-
-    ~StateConditionTracker();
-
-    bool init(const std::vector<Predicate>& allConditionConfig,
-              const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
-              std::vector<bool>& stack) override;
-
-    void evaluateCondition(const LogEvent& event,
-                           const std::vector<MatchingState>& eventMatcherValues,
-                           const std::vector<sp<ConditionTracker>>& mAllConditions,
-                           std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache) override;
-
-    /**
-     * Note: dimensionFields will be ignored in StateConditionTracker, because we demand metrics
-     * must take the entire dimension fields from StateConditionTracker. This is to make implementation
-     * simple and efficient.
-     *
-     * For example: wakelock duration by uid process states:
-     *              dimension in condition must be {uid, process state}.
-     */
-    void isConditionMet(const ConditionKey& conditionParameters,
-                        const std::vector<sp<ConditionTracker>>& allConditions,
-                        const bool isPartialLink,
-                        std::vector<ConditionState>& conditionCache) const override;
-
-    virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions) const {
-        return &mLastChangedToTrueDimensions;
-    }
-
-    virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions) const {
-        return &mLastChangedToFalseDimensions;
-    }
-
-    bool IsChangedDimensionTrackable() const  override { return true; }
-
-    bool IsSimpleCondition() const  override { return true; }
-
-    bool equalOutputDimensions(
-        const std::vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensions) const override {
-            return equalDimensions(mOutputDimensions, dimensions);
-    }
-
-    void getTrueSlicedDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions,
-            std::set<HashableDimensionKey>* dimensions) const override {
-        for (const auto& itr : mSlicedState) {
-            dimensions->insert(itr.second);
-        }
-    }
-
-private:
-    const ConfigKey mConfigKey;
-
-    // The index of the LogEventMatcher which defines the start.
-    int mStartLogMatcherIndex;
-
-    std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
-    std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
-
-    std::vector<Matcher> mOutputDimensions;
-    std::vector<Matcher> mPrimaryKeys;
-
-    ConditionState mInitialValue;
-
-    int mDimensionTag;
-
-    void dumpState();
-
-    bool hitGuardRail(const HashableDimensionKey& newKey);
-
-    // maps from [primary_key] to [primary_key, exclusive_state].
-    std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState;
-
-    FRIEND_TEST(StateConditionTrackerTest, TestStateChange);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index a3701a7..79a7e8d 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -41,15 +41,50 @@
 namespace os {
 namespace statsd {
 
+// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
+// pullAtomCallbackDied is never called.
+struct PullAtomCallbackDeathCookie {
+    PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey,
+                                const wp<StatsPuller>& puller)
+        : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+    }
+
+    sp<StatsPullerManager> mPullerManager;
+    PullerKey mPullerKey;
+    wp<StatsPuller> mPuller;
+};
+
+void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
+    PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
+    sp<StatsPullerManager>& thiz = cookie_->mPullerManager;
+    const PullerKey& pullerKey = cookie_->mPullerKey;
+    wp<StatsPuller> puller = cookie_->mPuller;
+
+    // Erase the mapping from the puller key to the puller if the mapping still exists.
+    // Note that we are removing the StatsPuller object, which internally holds the binder
+    // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works.
+    lock_guard<mutex> lock(thiz->mLock);
+    const auto& it = thiz->kAllPullAtomInfo.find(pullerKey);
+    if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) {
+        StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag,
+                                                                         /*registered=*/false);
+        thiz->kAllPullAtomInfo.erase(pullerKey);
+    }
+    // The death recipient corresponding to this specific IPullAtomCallback can never
+    // be triggered again, so free up resources.
+    delete cookie_;
+}
+
 // Values smaller than this may require to update the alarm.
 const int64_t NO_ALARM_UPDATE = INT64_MAX;
 
 StatsPullerManager::StatsPullerManager()
     : kAllPullAtomInfo({
               // TrainInfo.
-              {{.atomTag = util::TRAIN_INFO, .uid = -1}, new TrainInfoPuller()},
+              {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
       }),
-      mNextPullTimeNs(NO_ALARM_UPDATE) {
+      mNextPullTimeNs(NO_ALARM_UPDATE),
+      mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) {
 }
 
 bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey,
@@ -310,19 +345,28 @@
                                                   bool useUid) {
     std::lock_guard<std::mutex> _l(mLock);
     VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
-    // TODO(b/146439412): linkToDeath with the callback so that we can remove it
-    // and delete the puller.
+
     StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
     int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs;
     int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs;
-    kAllPullAtomInfo[{.atomTag = atomTag, .uid = useUid ? uid : -1}] = new StatsCallbackPuller(
-            atomTag, callback, actualCoolDownNs, actualTimeoutNs, additiveFields);
+
+    sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs,
+                                                             actualTimeoutNs, additiveFields);
+    PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1};
+    AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(),
+                         new PullAtomCallbackDeathCookie(this, key, puller));
+    kAllPullAtomInfo[key] = puller;
 }
 
-void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
+void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag,
+                                                    bool useUids) {
     std::lock_guard<std::mutex> _l(mLock);
-    StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
-    kAllPullAtomInfo.erase({.atomTag = atomTag});
+    PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1};
+    if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
+        StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag,
+                                                                         /*registered=*/false);
+        kAllPullAtomInfo.erase(key);
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index c5824a8..ab0ccee 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -101,11 +101,11 @@
     // If the metric wants to make any change to the data, like timestamps, they
     // should make a copy as this data may be shared with multiple metrics.
     virtual bool Pull(int tagId, const ConfigKey& configKey,
-                      vector<std::shared_ptr<LogEvent>>* data, bool useUids = false);
+                      vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
 
     // Same as above, but directly specify the allowed uids to pull from.
     virtual bool Pull(int tagId, const vector<int32_t>& uids,
-                      vector<std::shared_ptr<LogEvent>>* data, bool useUids = false);
+                      vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
 
     // Clear pull data cache immediately.
     int ForceClearPullerCache();
@@ -118,9 +118,9 @@
     void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
                                   const int64_t timeoutNs, const vector<int32_t>& additiveFields,
                                   const shared_ptr<IPullAtomCallback>& callback,
-                                  bool useUid = false);
+                                  bool useUid = true);
 
-    void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
+    void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = true);
 
     std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
 
@@ -152,7 +152,7 @@
     std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders;
 
     bool PullLocked(int tagId, const ConfigKey& configKey, vector<std::shared_ptr<LogEvent>>* data,
-                    bool useUids = false);
+                    bool useUids = true);
 
     bool PullLocked(int tagId, const vector<int32_t>& uids, vector<std::shared_ptr<LogEvent>>* data,
                     bool useUids);
@@ -164,6 +164,15 @@
 
     int64_t mNextPullTimeNs;
 
+    // Death recipient that is triggered when the process holding the IPullAtomCallback has died.
+    ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient;
+
+    /**
+     * Death recipient callback that is called when a pull atom callback dies.
+     * The cookie is a pointer to a PullAtomCallbackDeathCookie.
+     */
+    static void pullAtomCallbackDied(void* cookie);
+
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index e85b975..0de92f3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -55,6 +55,7 @@
 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
 const int FIELD_ID_BUCKET_INFO = 3;
 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
+const int FIELD_ID_SLICE_BY_STATE = 6;
 // for DurationBucketInfo
 const int FIELD_ID_DURATION = 3;
 const int FIELD_ID_BUCKET_NUM = 4;
@@ -115,6 +116,14 @@
     }
     mUnSlicedPartCondition = ConditionState::kUnknown;
 
+    for (const auto& stateLink : metric.state_link()) {
+        Metric2State ms;
+        ms.stateAtomId = stateLink.state_atom_id();
+        translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+        translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+        mMetric2StateLinks.push_back(ms);
+    }
+
     mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
     if (mWizard != nullptr && mConditionTrackerIndex >= 0 &&
             mMetric2ConditionLinks.size() == 1) {
@@ -150,21 +159,49 @@
     return anomalyTracker;
 }
 
+void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                                            const HashableDimensionKey& primaryKey,
+                                            const int32_t oldState, const int32_t newState) {
+    // Create a FieldValue object to hold the new state.
+    FieldValue value;
+    value.mValue.setInt(newState);
+    // Check if this metric has a StateMap. If so, map the new state value to
+    // the correct state group id.
+    mapStateValue(atomId, &value);
+
+    flushIfNeededLocked(eventTimeNs);
+
+    // Each duration tracker is mapped to a different whatKey (a set of values from the
+    // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the
+    // state change event are a subset of the tracker's whatKey field values.
+    //
+    // Ex. For a duration metric dimensioned on uid and tag:
+    // DurationTracker1 whatKey = uid: 1001, tag: 1
+    // DurationTracker2 whatKey = uid: 1002, tag 1
+    //
+    // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state
+    // change.
+    for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+        if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
+            continue;
+        }
+        whatIt.second->onStateChanged(eventTimeNs, atomId, value);
+    }
+}
+
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
         const MetricDimensionKey& eventKey) const {
     switch (mAggregationType) {
         case DurationMetric_AggregationType_SUM:
             return make_unique<OringDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
         case DurationMetric_AggregationType_MAX_SPARSE:
             return make_unique<MaxDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
     }
 }
 
@@ -364,6 +401,13 @@
             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
         }
+        // Then fill slice_by_state.
+        for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+            uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                     FIELD_ID_SLICE_BY_STATE);
+            writeStateToProto(state, protoOutput);
+            protoOutput->end(stateToken);
+        }
         // Then fill bucket_info (DurationBucketInfo).
         for (const auto& bucket : pair.second) {
             uint64_t bucketInfoToken = protoOutput->start(
@@ -460,7 +504,6 @@
                                               const ConditionKey& conditionKeys,
                                               bool condition, const LogEvent& event) {
     const auto& whatKey = eventKey.getDimensionKeyInWhat();
-
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         if (hitGuardRailLocked(eventKey)) {
@@ -471,19 +514,18 @@
 
     auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (mUseWhatDimensionAsInternalDimension) {
-        it->second->noteStart(whatKey, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
         return;
     }
 
     if (mInternalDimensions.empty()) {
-        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     } else {
         HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
         filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
-        it->second->noteStart(
-            dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     }
 
 }
@@ -519,6 +561,41 @@
         filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
     }
 
+    // Stores atom id to primary key pairs for each state atom that the metric is
+    // sliced by.
+    std::map<int, HashableDimensionKey> statePrimaryKeys;
+
+    // For states with primary fields, use MetricStateLinks to get the primary
+    // field values from the log event. These values will form a primary key
+    // that will be used to query StateTracker for the correct state value.
+    for (const auto& stateLink : mMetric2StateLinks) {
+        getDimensionForState(event.getValues(), stateLink,
+                             &statePrimaryKeys[stateLink.stateAtomId]);
+    }
+
+    // For each sliced state, query StateTracker for the state value using
+    // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
+    //
+    // Expected functionality: for any case where the MetricStateLinks are
+    // initialized incorrectly (ex. # of state links != # of primary fields, no
+    // links are provided for a state with primary fields, links are provided
+    // in the wrong order, etc.), StateTracker will simply return kStateUnknown
+    // when queried using an incorrect key.
+    HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY;
+    for (auto atomId : mSlicedStateAtoms) {
+        FieldValue value;
+        if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
+            // found a primary key for this state, query using the key
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
+        } else {
+            // if no MetricStateLinks exist for this state atom,
+            // query using the default dimension key (empty HashableDimensionKey)
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+        }
+        mapStateValue(atomId, &value);
+        stateValuesKey.addValue(value);
+    }
+
     // Handles Stop events.
     if (matcherIndex == mStopIndex) {
         if (mUseWhatDimensionAsInternalDimension) {
@@ -559,8 +636,8 @@
 
     condition = condition && mIsActive;
 
-    handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
-                     condition, event);
+    handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
+                     event);
 }
 
 size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 06da0f6..cc48f99 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -54,6 +54,10 @@
     sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
                                          const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
 
+    void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                        const HashableDimensionKey& primaryKey, const int32_t oldState,
+                        const int32_t newState) override;
+
 protected:
     void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
 
@@ -137,7 +141,7 @@
 
     // Helper function to create a duration tracker given the metric aggregation type.
     std::unique_ptr<DurationTracker> createDurationTracker(
-        const MetricDimensionKey& eventKey) const;
+            const MetricDimensionKey& eventKey) const;
 
     // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
     std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index be754e2..2518d85 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -120,12 +120,13 @@
         FieldValue value;
         if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
             // found a primary key for this state, query using the key
-            getMappedStateValue(atomId, statePrimaryKeys[atomId], &value);
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
         } else {
             // if no MetricStateLinks exist for this state atom,
             // query using the default dimension key (empty HashableDimensionKey)
-            getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
         }
+        mapStateValue(atomId, &value);
         stateValuesKey.addValue(value);
     }
 
@@ -264,15 +265,17 @@
     }
 }
 
-void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
-                                         FieldValue* value) {
+void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                                     FieldValue* value) {
     if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
         value->mValue = Value(StateTracker::kStateUnknown);
         value->mField.setTag(atomId);
         ALOGW("StateTracker not found for state atom %d", atomId);
         return;
     }
+}
 
+void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
     // check if there is a state map for this atom
     auto atomIt = mStateGroupMap.find(atomId);
     if (atomIt == mStateGroupMap.end()) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 4c4cd89..4550e65 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -187,7 +187,8 @@
     };
 
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                        const HashableDimensionKey& primaryKey, int oldState, int newState){};
+                        const HashableDimensionKey& primaryKey, const int32_t oldState,
+                        const int32_t newState){};
 
     // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
     // This method clears all the past buckets.
@@ -379,11 +380,15 @@
         return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
     }
 
-    // Query StateManager for original state value.
-    // If no state map exists for this atom, return the original value.
-    // Otherwise, return the group_id mapped to the atom and original value.
-    void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
-                             FieldValue* value);
+    // Query StateManager for original state value using the queryKey.
+    // The field and value are output.
+    void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                         FieldValue* value);
+
+    // If a state map exists for the given atom, replace the original state
+    // value with the group id mapped to the value.
+    // If no state map exists, keep the original state value.
+    void mapStateValue(const int32_t atomId, FieldValue* value);
 
     DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
 
@@ -467,6 +472,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 3fb9166..1fd6572 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -329,6 +329,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index afe93d4..8d59d13 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -56,11 +56,19 @@
     int64_t mDuration;
 };
 
+struct DurationValues {
+    // Recorded duration for current partial bucket.
+    int64_t mDuration;
+
+    // Sum of past partial bucket durations in current full bucket.
+    // Used for anomaly detection.
+    int64_t mDurationFullBucket;
+};
+
 class DurationTracker {
 public:
     DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-                    sp<ConditionWizard> wizard, int conditionIndex,
-                    bool nesting,
+                    sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                     int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
                     int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                     const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
@@ -73,7 +81,6 @@
           mNested(nesting),
           mCurrentBucketStartTimeNs(currentBucketStartNs),
           mDuration(0),
-          mDurationFullBucket(0),
           mCurrentBucketNum(currentBucketNum),
           mStartTimeNs(startTimeNs),
           mConditionSliced(conditionSliced),
@@ -82,8 +89,8 @@
 
     virtual ~DurationTracker(){};
 
-    virtual void noteStart(const HashableDimensionKey& key, bool condition,
-                           const int64_t eventTime, const ConditionKey& conditionKey) = 0;
+    virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
+                           const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                           const bool stopAll) = 0;
     virtual void noteStopAll(const int64_t eventTime) = 0;
@@ -91,6 +98,9 @@
     virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0;
     virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0;
 
+    virtual void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                const FieldValue& newState) = 0;
+
     // Flush stale buckets if needed, and return true if the tracker has no on-going duration
     // events, so that the owner can safely remove the tracker.
     virtual bool flushIfNeeded(
@@ -109,9 +119,12 @@
     // Dump internal states for debugging
     virtual void dumpStates(FILE* out, bool verbose) const = 0;
 
-    void setEventKey(const MetricDimensionKey& eventKey) {
-         mEventKey = eventKey;
-    }
+    virtual int64_t getCurrentStateKeyDuration() const = 0;
+
+    virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0;
+
+    // Replace old value with new value for the given state atom.
+    virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
 
 protected:
     int64_t getCurrentBucketEndTimeNs() const {
@@ -140,10 +153,11 @@
         }
     }
 
-    void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+    void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey,
+                                        const int64_t& bucketValue, const int64_t& bucketNum) {
         for (auto& anomalyTracker : mAnomalyTrackers) {
             if (anomalyTracker != nullptr) {
-                anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+                anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum);
             }
         }
     }
@@ -164,6 +178,10 @@
         return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
     }
 
+    void setEventKey(const MetricDimensionKey& eventKey) {
+        mEventKey = eventKey;
+    }
+
     // A reference to the DurationMetricProducer's config key.
     const ConfigKey& mConfigKey;
 
@@ -183,7 +201,8 @@
 
     int64_t mDuration;  // current recorded duration result (for partial bucket)
 
-    int64_t mDurationFullBucket;  // Sum of past partial buckets in current full bucket.
+    // Recorded duration results for each state key in the current partial bucket.
+    std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap;
 
     int64_t mCurrentBucketNum;
 
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 2be5855..ee4e167 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -26,15 +26,14 @@
 
 MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
                                        const MetricDimensionKey& eventKey,
-                                       sp<ConditionWizard> wizard, int conditionIndex,
-                                       bool nesting,
+                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        int64_t currentBucketStartNs, int64_t currentBucketNum,
                                        int64_t startTimeNs, int64_t bucketSizeNs,
                                        bool conditionSliced, bool fullLink,
                                        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers) {
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers) {
 }
 
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -91,7 +90,6 @@
     }
 }
 
-
 void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                                   bool forceStop) {
     VLOG("MaxDuration: key %s stop", key.toString().c_str());
@@ -240,6 +238,11 @@
     }
 }
 
+void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                        const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
     for (auto& pair : mInfos) {
         noteConditionChanged(pair.first, condition, timestamp);
@@ -309,6 +312,20 @@
     fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
 }
 
+int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index efb8dc7..2891c6e 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -54,10 +54,19 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 private:
     // Returns true if at least one of the mInfos is started.
     bool anyStarted();
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 57f3965..19b2fe8 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,13 +26,12 @@
 
 OringDurationTracker::OringDurationTracker(
         const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-        sp<ConditionWizard> wizard, int conditionIndex,
-        bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-        int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
-        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers),
+        sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+        int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
+        bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
@@ -90,10 +89,14 @@
             mConditionKeyMap.erase(key);
         }
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
-            VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
-                 (long long)mDuration);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
         }
     }
 
@@ -112,10 +115,14 @@
 
 void OringDurationTracker::noteStopAll(const int64_t timestamp) {
     if (!mStarted.empty()) {
-        mDuration += (timestamp - mLastStartTime);
-        VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
-             (long long)mDuration);
-        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (timestamp - mLastStartTime);
+        VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s",
+             (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+             mEventKey.getStateValuesKey().toString().c_str());
+        detectAndDeclareAnomaly(
+                timestamp, mCurrentBucketNum,
+                getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
     }
 
     stopAnomalyAlarm(timestamp);
@@ -146,21 +153,36 @@
 
     // Process the current bucket.
     if (mStarted.size() > 0) {
-        mDuration += (currentBucketEndTimeNs - mLastStartTime);
+        // Calculate the duration for the current state key.
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (currentBucketEndTimeNs - mLastStartTime);
     }
-    if (mDuration > 0) {
-        DurationBucket current_info;
-        current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
-        current_info.mBucketEndNs = currentBucketEndTimeNs;
-        current_info.mDuration = mDuration;
-        (*output)[mEventKey].push_back(current_info);
-        mDurationFullBucket += mDuration;
-        VLOG("  duration: %lld", (long long)current_info.mDuration);
-    }
-    if (eventTimeNs > fullBucketEnd) {
-        // End of full bucket, can send to anomaly tracker now.
-        addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum);
-        mDurationFullBucket = 0;
+    // Store DurationBucket info for each whatKey, stateKey pair.
+    // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the
+    // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to
+    // store durations for each stateKey, so we need to flush the bucket by creating a
+    // DurationBucket for each stateKey.
+    for (auto& durationIt : mStateKeyDurationMap) {
+        if (durationIt.second.mDuration > 0) {
+            DurationBucket current_info;
+            current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+            current_info.mBucketEndNs = currentBucketEndTimeNs;
+            current_info.mDuration = durationIt.second.mDuration;
+            (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)]
+                    .push_back(current_info);
+
+            durationIt.second.mDurationFullBucket += durationIt.second.mDuration;
+            VLOG("  duration: %lld", (long long)current_info.mDuration);
+        }
+
+        if (eventTimeNs > fullBucketEnd) {
+            // End of full bucket, can send to anomaly tracker now.
+            addPastBucketToAnomalyTrackers(
+                    MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first),
+                    getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum);
+            durationIt.second.mDurationFullBucket = 0;
+        }
+        durationIt.second.mDuration = 0;
     }
 
     if (mStarted.size() > 0) {
@@ -169,20 +191,19 @@
             info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
             info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
             info.mDuration = mBucketSizeNs;
+            // Full duration buckets are attributed to the current stateKey.
             (*output)[mEventKey].push_back(info);
             // Safe to send these buckets to anomaly tracker since they must be full buckets.
             // If it's a partial bucket, numBucketsForward would be 0.
-            addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i);
+            addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i);
             VLOG("  add filling bucket with duration %lld", (long long)info.mDuration);
         }
     } else {
         if (numBucketsForward >= 2) {
-            addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1);
+            addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1);
         }
     }
 
-    mDuration = 0;
-
     if (numBucketsForward > 0) {
         mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
         mCurrentBucketNum += numBucketsForward;
@@ -229,10 +250,14 @@
         }
 
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
-                 (long long)mDuration);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
 
@@ -288,10 +313,13 @@
     } else {
         if (!mStarted.empty()) {
             VLOG("Condition false, all paused");
-            mDuration += (timestamp - mLastStartTime);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
             mPaused.insert(mStarted.begin(), mStarted.end());
             mStarted.clear();
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
     if (mStarted.empty()) {
@@ -299,6 +327,20 @@
     }
 }
 
+void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                          const FieldValue& newState) {
+    // If no keys are being tracked, update the current state key and return.
+    if (mStarted.empty()) {
+        updateCurrentStateKey(atomId, newState);
+        return;
+    }
+    // Add the current duration length to the previous state key and then update
+    // the last start time and current state key.
+    mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime);
+    mLastStartTime = timestamp;
+    updateCurrentStateKey(atomId, newState);
+}
+
 int64_t OringDurationTracker::predictAnomalyTimestampNs(
         const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const {
 
@@ -308,12 +350,13 @@
     // The timestamp of the current bucket end.
     const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs();
 
-    // The past duration ns for the current bucket.
-    int64_t currentBucketPastNs = mDuration + mDurationFullBucket;
+    // The past duration ns for the current bucket of the current stateKey.
+    int64_t currentStateBucketPastNs =
+            getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration();
 
     // As we move into the future, old buckets get overwritten (so their old data is erased).
     // Sum of past durations. Will change as we overwrite old buckets.
-    int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
+    int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
 
     // The refractory period end timestamp for dimension mEventKey.
     const int64_t refractoryPeriodEndNs =
@@ -372,7 +415,7 @@
                     mEventKey,
                     mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx);
         } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) {
-            pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs));
+            pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs));
         }
     }
 
@@ -382,7 +425,34 @@
 void OringDurationTracker::dumpStates(FILE* out, bool verbose) const {
     fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size());
     fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size());
-    fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
+    fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration());
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDuration;
+    }
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDurationFullBucket;
+    }
+}
+
+void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey();
+    for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) {
+        if (stateValuesKey->getValues()[i].mField.getTag() == atomId) {
+            stateValuesKey->mutableValue(i)->mValue = newState.mValue;
+        }
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index f44e327..bd8017a 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -28,10 +28,9 @@
 public:
     OringDurationTracker(const ConfigKey& key, const int64_t& id,
                          const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                         int conditionIndex,
-                         bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-                         int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
-                         bool fullLink,
+                         int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+                         int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
+                         bool conditionSliced, bool fullLink,
                          const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
@@ -45,6 +44,9 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     bool flushCurrentBucket(
             const int64_t& eventTimeNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
@@ -56,6 +58,12 @@
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 private:
     // We don't need to keep track of individual durations. The information that's needed is:
     // 1) which keys are started. We record the first start time.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3810c48..2fcb13b 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -26,7 +26,6 @@
 #include "MetricProducer.h"
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
-#include "condition/StateConditionTracker.h"
 #include "external/StatsPullerManager.h"
 #include "matchers/CombinationLogMatchingTracker.h"
 #include "matchers/EventMatcherWizard.h"
@@ -283,49 +282,6 @@
     return true;
 }
 
-/**
- * A StateConditionTracker is built from a SimplePredicate which has only "start", and no "stop"
- * or "stop_all". The start must be an atom matcher that matches a state atom. It must
- * have dimension, the dimension must be the state atom's primary fields plus exclusive state
- * field. For example, the StateConditionTracker is used in tracking UidProcessState and ScreenState.
- *
- */
-bool isStateConditionTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) {
-    // 1. must not have "stop". must have "dimension"
-    if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) {
-        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(
-                simplePredicate.dimensions().field());
-        // 2. must be based on a state atom.
-        if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
-            // 3. dimension must be primary fields + state field IN ORDER
-            size_t expectedDimensionCount = it->second.primaryFields.size() + 1;
-            vector<Matcher> dimensions;
-            translateFieldMatcher(simplePredicate.dimensions(), &dimensions);
-            if (dimensions.size() != expectedDimensionCount) {
-                return false;
-            }
-            // 3.1 check the primary fields first.
-            size_t index = 0;
-            for (const auto& field : it->second.primaryFields) {
-                Matcher matcher = getSimpleMatcher(it->first, field);
-                if (!(matcher == dimensions[index])) {
-                    return false;
-                }
-                primaryKeys->push_back(matcher);
-                index++;
-            }
-            Matcher stateFieldMatcher =
-                    getSimpleMatcher(it->first, it->second.exclusiveField);
-            // 3.2 last dimension should be the exclusive field.
-            if (!(dimensions.back() == stateFieldMatcher)) {
-                return false;
-            }
-            return true;
-        }
-    }
-    return false;
-}  // namespace statsd
-
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
                     const unordered_map<int64_t, int>& logTrackerMap,
                     unordered_map<int64_t, int>& conditionTrackerMap,
@@ -341,16 +297,8 @@
         int index = allConditionTrackers.size();
         switch (condition.contents_case()) {
             case Predicate::ContentsCase::kSimplePredicate: {
-                vector<Matcher> primaryKeys;
-                if (isStateConditionTracker(condition.simple_predicate(), &primaryKeys)) {
-                    allConditionTrackers.push_back(new StateConditionTracker(key, condition.id(), index,
-                                                                    condition.simple_predicate(),
-                                                                    logTrackerMap, primaryKeys));
-                } else {
-                    allConditionTrackers.push_back(new SimpleConditionTracker(
-                            key, condition.id(), index, condition.simple_predicate(),
-                            logTrackerMap));
-                }
+                allConditionTrackers.push_back(new SimpleConditionTracker(
+                        key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
                 break;
             }
             case Predicate::ContentsCase::kCombination: {
@@ -564,6 +512,33 @@
             }
         }
 
+        std::vector<int> slicedStateAtoms;
+        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+        if (metric.slice_by_state_size() > 0) {
+            if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
+                ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
+                return false;
+            }
+            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+                return false;
+            }
+        } else {
+            if (metric.state_link_size() > 0) {
+                ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
+                return false;
+            }
+        }
+
+        // Check that all metric state links are a subset of dimensions_in_what fields.
+        std::vector<Matcher> dimensionsInWhat;
+        translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+        for (const auto& stateLink : metric.state_link()) {
+            if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+                return false;
+            }
+        }
+
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(config, metric.id(), metricIndex,
@@ -575,7 +550,8 @@
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
                 key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
                 trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs,
-                currentTimeNs, eventActivationMap, eventDeactivationMap);
+                currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+                stateGroupMap);
 
         allMetricProducers.push_back(durationMetric);
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index a8ccc62..6af7a9a 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -132,8 +132,6 @@
                       vector<int>& metricsWithActivation,
                       std::set<int64_t>& noReportMetricIds);
 
-bool isStateConditionTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index c45274e..ed98f50 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -103,12 +103,14 @@
 message DurationMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+  repeated StateValue slice_by_state = 6;
 
   repeated DurationBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
   repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 83d9484..c7407bd 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -227,8 +227,12 @@
 
   optional int64 condition = 3;
 
+  repeated int64 slice_by_state = 9;
+
   repeated MetricConditionLink links = 4;
 
+  repeated MetricStateLink state_link = 10;
+
   enum AggregationType {
     SUM = 1;
 
@@ -238,9 +242,9 @@
 
   optional FieldMatcher dimensions_in_what = 6;
 
-  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
-
   optional TimeUnit bucket = 7;
+
+  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
 }
 
 message GaugeMetric {
diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp
new file mode 100644
index 0000000..29adcd0
--- /dev/null
+++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+#include "src/HashableDimensionKey.h"
+
+#include <gtest/gtest.h>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+using android::util::ProtoReader;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Test that #containsLinkedStateValues returns false when the whatKey is
+ * smaller than the primaryKey.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) {
+    std::vector<Metric2State> mMetric2StateLinks;
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY;
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks,
+                                           UID_PROCESS_STATE_ATOM_ID));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when the linked values
+ * are not equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    FieldMatcher whatMatcher;
+    whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+    FieldMatcher* child11 = whatMatcher.add_child();
+    child11->set_field(1);
+
+    FieldMatcher stateMatcher;
+    stateMatcher.set_field(stateAtomId);
+    FieldMatcher* child21 = stateMatcher.add_child();
+    child21->set_field(1);
+
+    std::vector<Metric2State> mMetric2StateLinks;
+    Metric2State ms;
+    ms.stateAtomId = stateAtomId;
+    translateFieldMatcher(whatMatcher, &ms.metricFields);
+    translateFieldMatcher(stateMatcher, &ms.stateFields);
+    mMetric2StateLinks.push_back(ms);
+
+    int32_t uid1 = 1000;
+    int32_t uid2 = 1001;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid2, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when there is no link
+ * between the key values.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    std::vector<Metric2State> mMetric2StateLinks;
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid1, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns true when the key values are
+ * linked and equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    FieldMatcher whatMatcher;
+    whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+    FieldMatcher* child11 = whatMatcher.add_child();
+    child11->set_field(1);
+
+    FieldMatcher stateMatcher;
+    stateMatcher.set_field(stateAtomId);
+    FieldMatcher* child21 = stateMatcher.add_child();
+    child21->set_field(1);
+
+    std::vector<Metric2State> mMetric2StateLinks;
+    Metric2State ms;
+    ms.stateAtomId = stateAtomId;
+    translateFieldMatcher(whatMatcher, &ms.metricFields);
+    translateFieldMatcher(stateMatcher, &ms.stateFields);
+    mMetric2StateLinks.push_back(ms);
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid1, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
deleted file mode 100644
index 86b50ae8..0000000
--- a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/condition/StateConditionTracker.h"
-#include "tests/statsd_test_util.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <stdio.h>
-#include <numeric>
-#include <vector>
-
-using std::map;
-using std::unordered_map;
-using std::vector;
-
-#ifdef __ANDROID__
-namespace android {
-namespace os {
-namespace statsd {
-
-const int kUidProcTag = 27;
-
-SimplePredicate getUidProcStatePredicate() {
-    SimplePredicate simplePredicate;
-    simplePredicate.set_start(StringToId("UidProcState"));
-
-    simplePredicate.mutable_dimensions()->set_field(kUidProcTag);
-    simplePredicate.mutable_dimensions()->add_child()->set_field(1);
-    simplePredicate.mutable_dimensions()->add_child()->set_field(2);
-
-    simplePredicate.set_count_nesting(false);
-    return simplePredicate;
-}
-
-// TODO(b/149590301): Update these tests to use new socket schema.
-//void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) {
-//    event->write(uid);
-//    event->write(state);
-//    event->init();
-//}
-//
-//TEST(StateConditionTrackerTest, TestStateChange) {
-//    int uid1 = 111;
-//    int uid2 = 222;
-//
-//    int state1 = 1001;
-//    int state2 = 1002;
-//    unordered_map<int64_t, int> trackerNameIndexMap;
-//    trackerNameIndexMap[StringToId("UidProcState")] = 0;
-//    vector<Matcher> primaryFields;
-//    primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1));
-//    StateConditionTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(),
-//                         trackerNameIndexMap, primaryFields);
-//
-//    LogEvent event(kUidProcTag, 0 /*timestamp*/);
-//    makeUidProcStateEvent(uid1, state1, &event);
-//
-//    vector<MatchingState> matcherState;
-//    matcherState.push_back(MatchingState::kMatched);
-//    vector<sp<ConditionTracker>> allPredicates;
-//    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-//    vector<bool> changedCache(1, false);
-//
-//    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-//    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-//    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-//    EXPECT_TRUE(changedCache[0]);
-//
-//    changedCache[0] = false;
-//    conditionCache[0] = ConditionState::kNotEvaluated;
-//    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-//    EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size());
-//    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-//    EXPECT_FALSE(changedCache[0]);
-//
-//    LogEvent event2(kUidProcTag, 0 /*timestamp*/);
-//    makeUidProcStateEvent(uid1, state2, &event2);
-//
-//    changedCache[0] = false;
-//    conditionCache[0] = ConditionState::kNotEvaluated;
-//    tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache);
-//    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-//    EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size());
-//    EXPECT_TRUE(changedCache[0]);
-//
-//    LogEvent event3(kUidProcTag, 0 /*timestamp*/);
-//    makeUidProcStateEvent(uid2, state1, &event3);
-//    changedCache[0] = false;
-//    conditionCache[0] = ConditionState::kNotEvaluated;
-//    tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache);
-//    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-//    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-//    EXPECT_TRUE(changedCache[0]);
-//}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index ae2a0f5..2659944 100644
--- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -14,12 +14,13 @@
 
 #include <gtest/gtest.h>
 
+#include <vector>
+
 #include "src/StatsLogProcessor.h"
+#include "src/state/StateTracker.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
 
-#include <vector>
-
 namespace android {
 namespace os {
 namespace statsd {
@@ -101,7 +102,7 @@
             reports.reports(0).metrics(0).duration_metrics();
     EXPECT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
+    DurationMetricData data = durationMetrics.data(0);
     EXPECT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos());
     EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -183,7 +184,7 @@
             reports.reports(0).metrics(0).duration_metrics();
     EXPECT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
+    DurationMetricData data = durationMetrics.data(0);
     EXPECT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
@@ -353,7 +354,7 @@
             reports.reports(0).metrics(0).duration_metrics();
     EXPECT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
+    DurationMetricData data = durationMetrics.data(0);
     EXPECT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
@@ -434,7 +435,7 @@
     EXPECT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
 
     // Validate bucket info.
     EXPECT_EQ(1, data.bucket_info_size());
@@ -533,7 +534,7 @@
     EXPECT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
                                     util::WAKELOCK_STATE_CHANGED, appUid);
@@ -691,7 +692,7 @@
     EXPECT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
                                     util::WAKELOCK_STATE_CHANGED, appUid);
@@ -709,6 +710,734 @@
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos());
 }
 
+TEST(DurationMetricE2eTest, TestWithSlicedState) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto screenState = CreateScreenState();
+    *config.add_state() = screenState;
+
+    // Create duration metric that slices by screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->add_slice_by_state(screenState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+        ON              OFF     ON                                  (BatterySaverMode)
+      |          |                   |                              (ScreenIsOnEvent)
+           |                  |                                     (ScreenIsOffEvent)
+              |                                                     (ScreenDozeEvent)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                       // 0:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 50 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 80 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 1:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                         // 2:10
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 250 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));                       // 4:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary.
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 310 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 5:20
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);  // 6:10
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(2);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+    *config.add_predicate() = deviceUnpluggedPredicate;
+
+    auto screenState = CreateScreenState();
+    *config.add_state() = screenState;
+
+    // Create duration metric that has a condition and slices by screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->set_condition(deviceUnpluggedPredicate.id());
+    durationMetric->add_slice_by_state(screenState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |       1       2       3       4       5     6     7     8  (minutes)
+    |---------------------------------------|------------------
+         ON                              OFF    ON             (BatterySaverMode)
+                  T            F    T                          (DeviceUnpluggedPredicate)
+             |          |              |                       (ScreenIsOnEvent)
+                |           |                       |          (ScreenIsOffEvent)
+                                |                              (ScreenDozeEvent)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 60 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 1:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 80 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:30
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE));  // 2:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 145 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:35
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 170 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 3:00
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_USB));  // 3:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 200 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 3:30
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE));  // 4:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 260 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                         // 4:30
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary.
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC));  // 5:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 380 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 6:30
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(2);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto screenStateWithMap = CreateScreenStateWithOnOffMap();
+    *config.add_state() = screenStateWithMap;
+
+    // Create duration metric that slices by mapped screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->add_slice_by_state(screenStateWithMap.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+        ON              OFF     ON                                  (BatterySaverMode)
+     ---------------------------------------------------------SCREEN_OFF events
+           |                  |                                  (ScreenStateOffEvent = 1)
+              |                                                  (ScreenStateDozeEvent = 3)
+                                                |                (ScreenStateDozeSuspendEvent = 4)
+     ---------------------------------------------------------SCREEN_ON events
+      |          |                   |                           (ScreenStateOnEvent = 2)
+                      |                                          (ScreenStateVrEvent = 5)
+                                            |                    (ScreenStateOnSuspendEvent = 6)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                       // 0:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 70 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:20
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 100 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 1:50
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 170 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_VR));                         // 3:00
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 250 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));                       // 4:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary 5:10.
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 320 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 5:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 390 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND));  // 6:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 430 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND));  // 7:20
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(2, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create duration metric that slices by uid process state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState"));
+    durationMetric->set_what(holdingWakelockPredicate.id());
+    durationMetric->add_slice_by_state(uidProcessState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // The state has only one primary field (uid).
+    auto stateLink = durationMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // This config is rejected because the dimension in what fields are not a superset of the sliced
+    // state primary fields.
+    EXPECT_EQ(processor->mMetricsManagers.size(), 0);
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create duration metric that slices by uid process state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState"));
+    durationMetric->set_what(holdingWakelockPredicate.id());
+    durationMetric->add_slice_by_state(uidProcessState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // The metric is dimensioning by first uid of attribution node and tag.
+    *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions(
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */});
+    // The state has only one primary field (uid).
+    auto stateLink = durationMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+    // Initialize log events.
+    int appUid1 = 1001;
+    int appUid2 = 1002;
+    std::vector<int> attributionUids1 = {appUid1};
+    std::vector<string> attributionTags1 = {"App1"};
+
+    std::vector<int> attributionUids2 = {appUid2};
+    std::vector<string> attributionTags2 = {"App2"};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC, appUid1,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 0:20
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock1"));  // 0:30
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock2"));  // 0:35
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock1"));  // 0:40
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock2"));  // 0:45
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 50 * NS_PER_SEC, appUid2,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 1:00
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 60 * NS_PER_SEC, appUid1,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 1:10
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock1"));  // 1:50
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC, appUid2,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE));  // 2:10
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock2"));  // 3:30
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(9, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(2);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(3);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(4);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(5);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(6);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(7);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(8);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 100220b..d2f0f57 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -62,9 +62,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
     // Event starts again. This would not change anything as it already starts.
@@ -97,9 +96,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
 
@@ -132,9 +130,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // The event starts.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -172,9 +169,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // 2 starts
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -218,9 +214,8 @@
     int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
-                               false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               0, bucketStartTimeNs, bucketSizeNs, true, false, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
@@ -267,9 +262,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
@@ -326,9 +321,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
     tracker.noteConditionChanged(key1, true, conditionStarts1);
@@ -408,9 +403,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
     tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 1cd7bdb..39d3919 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -61,9 +61,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -92,9 +92,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -124,9 +123,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -154,9 +152,8 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -198,9 +195,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
 
@@ -237,9 +234,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
@@ -275,9 +272,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -316,9 +312,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                                 {anomalyTracker});
 
     // Nothing in the past bucket.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -422,9 +418,8 @@
             sp<AlarmMonitor> alarmMonitor;
             sp<DurationAnomalyTracker> anomalyTracker =
                 new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-            OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY,
-                                         wizard, 1,
-                                         true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+            OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard,
+                                         1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
                                          bucketSizeNs, true, false, {anomalyTracker});
 
             int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
@@ -481,15 +476,15 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 false, false, {anomalyTracker});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
     EXPECT_TRUE(tracker.mStarted.empty());
-    EXPECT_EQ(10LL, tracker.mDuration); // 10ns
+    EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration);  // 10ns
 
     EXPECT_EQ(0u, tracker.mStarted.size());
 
@@ -530,11 +525,11 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
+                                 false, {anomalyTracker});
 
-    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
+    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey);  // start key1
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
@@ -544,13 +539,13 @@
     EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
+    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey);  // start key1 again
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
+    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey);  // start key2
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index a0e0095..a5b8e1c 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -105,63 +105,6 @@
 }
 // END: build event functions.
 
-// START: get primary key functions
-void getUidProcessKey(int uid, HashableDimensionKey* key) {
-    int pos1[] = {1, 0, 0};
-    Field field1(27 /* atom id */, pos1, 0 /* depth */);
-    Value value1((int32_t)uid);
-
-    key->addValue(FieldValue(field1, value1));
-}
-
-void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
-    int pos1[] = {1, 0, 0};
-    int pos2[] = {2, 0, 0};
-
-    Field field1(59 /* atom id */, pos1, 0 /* depth */);
-    Field field2(59 /* atom id */, pos2, 0 /* depth */);
-
-    Value value1((int32_t)uid);
-    Value value2(packageName);
-
-    key->addValue(FieldValue(field1, value1));
-    key->addValue(FieldValue(field2, value2));
-}
-
-void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
-    int pos1[] = {1, 1, 1};
-    int pos3[] = {2, 0, 0};
-    int pos4[] = {3, 0, 0};
-
-    Field field1(10 /* atom id */, pos1, 2 /* depth */);
-
-    Field field3(10 /* atom id */, pos3, 0 /* depth */);
-    Field field4(10 /* atom id */, pos4, 0 /* depth */);
-
-    Value value1((int32_t)uid);
-    Value value3((int32_t)1 /*partial*/);
-    Value value4(tag);
-
-    key->addValue(FieldValue(field1, value1));
-    key->addValue(FieldValue(field3, value3));
-    key->addValue(FieldValue(field4, value4));
-}
-
-void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
-    int pos1[] = {1, 1, 1};
-    int pos3[] = {2, 0, 0};
-
-    Field field1(10 /* atom id */, pos1, 2 /* depth */);
-    Field field3(10 /* atom id */, pos3, 0 /* depth */);
-
-    Value value1((int32_t)uid);
-    Value value3((int32_t)1 /*partial*/);
-
-    key->addValue(FieldValue(field1, value1));
-    key->addValue(FieldValue(field3, value3));
-}
-// END: get primary key functions
-
 TEST(StateListenerTest, TestStateListenerWeakPointer) {
     sp<TestStateListener> listener = new TestStateListener();
     wp<TestStateListener> wListener = listener;
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index e3ce5c5..7d765d3 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -135,6 +135,27 @@
         "BatterySaverModeStop", BatterySaverModeStateChanged::OFF);
 }
 
+AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name,
+                                                 BatteryPluggedStateEnum state) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(util::PLUGGED_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(1);  // State field.
+    field_value_matcher->set_eq_int(state);
+    return atom_matcher;
+}
+
+AtomMatcher CreateBatteryStateNoneMatcher() {
+    return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone",
+                                                BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+}
+
+AtomMatcher CreateBatteryStateUsbMatcher() {
+    return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb",
+                                                BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+}
 
 AtomMatcher CreateScreenStateChangedAtomMatcher(
     const string& name, android::view::DisplayStateEnum state) {
@@ -234,6 +255,14 @@
     return predicate;
 }
 
+Predicate CreateDeviceUnpluggedPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("DeviceUnplugged"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb"));
+    return predicate;
+}
+
 Predicate CreateScreenIsOnPredicate() {
     Predicate predicate;
     predicate.set_id(StringToId("ScreenIsOn"));
@@ -410,6 +439,74 @@
     return dimensions;
 }
 
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+                                                    const std::vector<Position>& positions,
+                                                    const std::vector<int>& fields) {
+    FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions);
+
+    for (const int field : fields) {
+        dimensions.add_child()->set_field(field);
+    }
+    return dimensions;
+}
+
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    Field field1(27 /* atom id */, pos1, 0 /* depth */);
+    Value value1((int32_t)uid);
+
+    key->addValue(FieldValue(field1, value1));
+}
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    int pos2[] = {2, 0, 0};
+
+    Field field1(59 /* atom id */, pos1, 0 /* depth */);
+    Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value2(packageName);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field2, value2));
+}
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+    int pos4[] = {3, 0, 0};
+
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+    Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+    Value value4(tag);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+    key->addValue(FieldValue(field4, value4));
+}
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+}
+// END: get primary key functions
+
 shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
                                             int32_t value2) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -595,6 +692,23 @@
     return logEvent;
 }
 
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    logEvent->parseBuffer(buf, size);
+    AStatsEvent_release(statsEvent);
+    return logEvent;
+}
+
 std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
@@ -957,6 +1071,22 @@
     return static_cast<int64_t>(std::hash<std::string>()(str));
 }
 
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+                                                   const int uid, const string& tag) {
+    EXPECT_EQ(value.field(), atomId);
+    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 2);
+    // Attribution field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+    // Uid field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+              uid);
+    // Tag field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3);
+    EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag);
+}
+
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
     EXPECT_EQ(value.field(), atomId);
     EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 0c06936..f24705a 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -68,6 +68,12 @@
 // Create AtomMatcher proto for stopping battery save mode.
 AtomMatcher CreateBatterySaverModeStopAtomMatcher();
 
+// Create AtomMatcher proto for battery state none mode.
+AtomMatcher CreateBatteryStateNoneMatcher();
+
+// Create AtomMatcher proto for battery state usb mode.
+AtomMatcher CreateBatteryStateUsbMatcher();
+
 // Create AtomMatcher proto for process state changed.
 AtomMatcher CreateUidProcessStateChangedAtomMatcher();
 
@@ -110,6 +116,9 @@
 // Create Predicate proto for battery saver mode.
 Predicate CreateBatterySaverModePredicate();
 
+// Create Predicate proto for device unplogged mode.
+Predicate CreateDeviceUnpluggedPredicate();
+
 // Create Predicate proto for holding wakelock.
 Predicate CreateHoldingWakelockPredicate();
 
@@ -164,6 +173,22 @@
 FieldMatcher CreateAttributionUidDimensions(const int atomId,
                                             const std::vector<Position>& positions);
 
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+                                                    const std::vector<Position>& positions,
+                                                    const std::vector<int>& fields);
+
+// START: get primary key functions
+// These functions take in atom field information and create FieldValues which are stored in the
+// given HashableDimensionKey.
+void getUidProcessKey(int uid, HashableDimensionKey* key);
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key);
+// END: get primary key functions
+
 shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
                                             int32_t value2);
 
@@ -213,6 +238,9 @@
 // Create log event when battery saver stops.
 std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs);
 
+// Create log event when battery state changes.
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state);
+
 // Create log event for app moving to background.
 std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid);
 
@@ -274,6 +302,8 @@
 
 int64_t StringToId(const string& str);
 
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+                                                   const int uid, const string& tag);
 void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
 void ValidateAttributionUidAndTagDimension(
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
index a381f9c..2909048 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -17,16 +17,23 @@
 
 import com.android.os.StatsLog.ConfigMetricsReportList;
 
+import com.google.common.io.Files;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Formatter;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Utilities for local use of statsd.
@@ -80,7 +87,8 @@
      * @throws InterruptedException
      */
     public static ConfigMetricsReportList getReportList(long configId, boolean clearData,
-            boolean useShellUid, Logger logger) throws IOException, InterruptedException {
+            boolean useShellUid, Logger logger, String deviceSerial)
+            throws IOException, InterruptedException {
         try {
             File outputFile = File.createTempFile("statsdret", ".bin");
             outputFile.deleteOnExit();
@@ -88,6 +96,8 @@
                     outputFile,
                     logger,
                     "adb",
+                    "-s",
+                    deviceSerial,
                     "shell",
                     CMD_DUMP_REPORT,
                     useShellUid ? SHELL_UID : "",
@@ -117,12 +127,14 @@
      * @throws IOException
      * @throws InterruptedException
      */
-    public static void logAppBreadcrumb(int label, int state, Logger logger)
+    public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial)
             throws IOException, InterruptedException {
         runCommand(
                 null,
                 logger,
                 "adb",
+                "-s",
+                deviceSerial,
                 "shell",
                 CMD_LOG_APP_BREADCRUMB,
                 String.valueOf(label),
@@ -145,13 +157,14 @@
      * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName))
      * If all else fails, assume it will work (letting future commands deal with any errors).
      */
-    public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename) {
+    public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename,
+            String deviceSerial) {
         BufferedReader in = null;
         try {
             File outFileSdk = File.createTempFile("shelltools_sdk", "tmp");
             outFileSdk.deleteOnExit();
             runCommand(outFileSdk, logger,
-                    "adb", "shell", "getprop", "ro.build.version.sdk");
+                    "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk");
             in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk)));
             // If NullPointerException/NumberFormatException/etc., just catch and return true.
             int sdk = Integer.parseInt(in.readLine().trim());
@@ -162,7 +175,7 @@
                 File outFileCode = File.createTempFile("shelltools_codename", "tmp");
                 outFileCode.deleteOnExit();
                 runCommand(outFileCode, logger,
-                        "adb", "shell", "getprop", "ro.build.version.codename");
+                        "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename");
                 in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode)));
                 return in.readLine().startsWith(minCodename);
             } else {
@@ -190,4 +203,30 @@
             return record.getMessage() + "\n";
         }
     }
+
+    /**
+     * Parse the result of "adb devices" to return the list of connected devices.
+     * @param logger Logger to log error messages
+     * @return List of the serial numbers of the connected devices.
+     */
+    public static List<String> getDeviceSerials(Logger logger) {
+        try {
+            ArrayList<String> devices = new ArrayList<>();
+            File outFile = File.createTempFile("device_serial", "tmp");
+            outFile.deleteOnExit();
+            Utils.runCommand(outFile, logger, "adb", "devices");
+            List<String> outputLines = Files.readLines(outFile, Charset.defaultCharset());
+            Pattern regex = Pattern.compile("^(.*)\tdevice$");
+            for (String line : outputLines) {
+                Matcher m = regex.matcher(line);
+                if (m.find()) {
+                    devices.add(m.group(1));
+                }
+            }
+            return devices;
+        } catch (Exception ex) {
+            logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage());
+        }
+        return null;
+    }
 }
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
index 2eb4660..7db5141 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
@@ -26,6 +26,8 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.util.List;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
@@ -49,7 +51,7 @@
     public static final String HELP_STRING =
         "Usage:\n\n" +
 
-        "statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
         "  Uploads the given statsd config file (in binary or human-readable-text format).\n" +
         "  If a config with this id already exists, removes it first.\n" +
         "    CONFIG_FILE    Location of config file on host.\n" +
@@ -59,12 +61,12 @@
         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
         "  Same as upload, but does not remove the old config first (if it already exists).\n" +
         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
         "  Prints the output statslog data (in binary or human-readable-text format).\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         "    --binary       Output should be in binary, instead of default human-readable text.\n" +
@@ -75,13 +77,13 @@
         //                                                      --include_current_bucket --proto
         "\n" +
 
-        "statsd_localdrive remove [CONFIG_ID]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" +
         "  Removes the config.\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive clear [CONFIG_ID]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" +
         "  Clears the data associated with the config.\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID
@@ -94,29 +96,59 @@
     /** Usage: make statsd_localdrive && statsd_localdrive */
     public static void main(String[] args) {
         Utils.setUpLogger(sLogger, DEBUG);
+        if (args.length == 0) {
+            printHelp();
+            return;
+        }
 
-        if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) {
+        int remainingArgsLength = args.length;
+        String deviceSerial = null;
+        if (args[0].equals("-s")) {
+            if (args.length == 1) {
+                printHelp();
+            }
+            deviceSerial = args[1];
+            remainingArgsLength -= 2;
+        }
+
+        List<String> connectedDevices = Utils.getDeviceSerials(sLogger);
+        if (connectedDevices == null || connectedDevices.size() == 0) {
+            sLogger.log(Level.SEVERE, "No device connected.");
+            return;
+        }
+        if (connectedDevices.size() == 1 && deviceSerial == null) {
+            deviceSerial = connectedDevices.get(0);
+        }
+
+        if (deviceSerial == null) {
+            sLogger.log(Level.SEVERE, "More than one devices connected. Please specify"
+                    + " with -s DEVICE_SERIAL");
+            return;
+        }
+
+        if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) {
             sLogger.severe("LocalDrive only works with statsd versions for Android "
                     + MIN_CODENAME + " or higher.");
             return;
         }
 
-        if (args.length > 0) {
-            switch (args[0]) {
+        int idx = args.length - remainingArgsLength;
+        if (remainingArgsLength > 0) {
+            switch (args[idx]) {
                 case "clear":
-                    cmdClear(args);
+                    cmdClear(args, idx, deviceSerial);
                     return;
                 case "get-data":
-                    cmdGetData(args);
+                    cmdGetData(args, idx, deviceSerial);
                     return;
                 case "remove":
-                    cmdRemove(args);
+                    cmdRemove(args, idx);
                     return;
                 case "update":
-                    cmdUpdate(args);
+                    cmdUpdate(args, idx, deviceSerial);
                     return;
                 case "upload":
-                    cmdUpload(args);
+                    cmdUpload(args, idx, deviceSerial);
                     return;
             }
         }
@@ -128,17 +160,18 @@
     }
 
     // upload CONFIG_FILE [CONFIG_ID] [--binary]
-    private static boolean cmdUpload(String[] args) {
-        return updateConfig(args, true);
+    private static boolean cmdUpload(String[] args, int idx, String deviceSerial) {
+        return updateConfig(args, idx, true, deviceSerial);
     }
 
     // update CONFIG_FILE [CONFIG_ID] [--binary]
-    private static boolean cmdUpdate(String[] args) {
-        return updateConfig(args, false);
+    private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) {
+        return updateConfig(args, idx, false, deviceSerial);
     }
 
-    private static boolean updateConfig(String[] args, boolean removeOldConfig) {
-        int argCount = args.length - 1; // Used up one for upload/update.
+    private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig,
+            String deviceSerial) {
+        int argCount = args.length - 1 - idx; // Used up one for upload/update.
 
         // Get CONFIG_FILE
         if (argCount < 1) {
@@ -146,7 +179,7 @@
             printHelp();
             return false;
         }
-        final String origConfigLocation = args[1];
+        final String origConfigLocation = args[idx + 1];
         if (!new File(origConfigLocation).exists()) {
             sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation);
             return false;
@@ -154,13 +187,13 @@
         argCount--;
 
         // Get --binary
-        boolean binary = contains(args, 2, BINARY_FLAG);
+        boolean binary = contains(args, idx + 2, BINARY_FLAG);
         if (binary) argCount --;
 
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(argCount < 1, args, 2);
+            configId = getConfigId(argCount < 1, args, idx + 2);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -174,7 +207,8 @@
             try {
                 Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
                         Utils.SHELL_UID, String.valueOf(configId));
-                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
+                        deviceSerial);
             } catch (InterruptedException | IOException e) {
                 sLogger.severe("Failed to remove config: " + e.getMessage());
                 return false;
@@ -218,19 +252,19 @@
     }
 
     // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]
-    private static boolean cmdGetData(String[] args) {
-        boolean binary = contains(args, 1, BINARY_FLAG);
-        boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG);
-        boolean clearData = contains(args, 1, CLEAR_DATA);
+    private static boolean cmdGetData(String[] args, int idx, String deviceSerial) {
+        boolean binary = contains(args, idx + 1, BINARY_FLAG);
+        boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG);
+        boolean clearData = contains(args, idx + 1, CLEAR_DATA);
 
         // Get CONFIG_ID
-        int argCount = args.length - 1; // Used up one for get-data.
+        int argCount = args.length - 1 - idx; // Used up one for get-data.
         if (binary) argCount--;
         if (noUidMap) argCount--;
         if (clearData) argCount--;
         long configId;
         try {
-            configId = getConfigId(argCount < 1, args, 1);
+            configId = getConfigId(argCount < 1, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -243,7 +277,8 @@
         // Even if the args request no modifications, we still parse it to make sure it's valid.
         ConfigMetricsReportList reportList;
         try {
-            reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger);
+            reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger,
+                    deviceSerial);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
@@ -274,11 +309,11 @@
     }
 
     // clear [CONFIG_ID]
-    private static boolean cmdClear(String[] args) {
+    private static boolean cmdClear(String[] args, int idx, String deviceSerial) {
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(false, args, 1);
+            configId = getConfigId(false, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -287,7 +322,8 @@
         sLogger.fine(String.format("cmdClear with %d", configId));
 
         try {
-            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
+                    deviceSerial);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
@@ -296,11 +332,11 @@
     }
 
     // remove [CONFIG_ID]
-    private static boolean cmdRemove(String[] args) {
+    private static boolean cmdRemove(String[] args, int idx) {
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(false, args, 1);
+            configId = getConfigId(false, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 75518a3..2a7cfd3 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -37,6 +37,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -71,6 +72,7 @@
     private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
 
     private String mAdditionalAllowedPackage;
+    private String mDeviceSerial;
     private final Set<Long> mTrackedMetrics = new HashSet<>();
 
     public static void main(String[] args) {
@@ -81,15 +83,41 @@
 
         if (args.length < 1) {
             LOGGER.log(Level.SEVERE, "Usage: ./test_drive [-p additional_allowed_package] "
+                    + "[-s DEVICE_SERIAL_NUMBER]"
                     + "<atomId1> <atomId2> ... <atomIdN>");
             return;
         }
 
-        if (args.length >= 3 && args[0].equals("-p")) {
-            testDrive.mAdditionalAllowedPackage = args[1];
+        List<String> connectedDevices = Utils.getDeviceSerials(LOGGER);
+        if (connectedDevices == null || connectedDevices.size() == 0) {
+            LOGGER.log(Level.SEVERE, "No device connected.");
+            return;
         }
 
-        for (int i = testDrive.mAdditionalAllowedPackage == null ? 0 : 2; i < args.length; i++) {
+        int arg_index = 0;
+        while (arg_index < args.length) {
+            String arg = args[arg_index];
+            if (arg.equals("-p")) {
+                testDrive.mAdditionalAllowedPackage = args[++arg_index];
+            } else if (arg.equals("-s")) {
+                testDrive.mDeviceSerial = args[++arg_index];
+            } else {
+                break;
+            }
+            arg_index++;
+        }
+
+        if (connectedDevices.size() == 1 && testDrive.mDeviceSerial == null) {
+            testDrive.mDeviceSerial = connectedDevices.get(0);
+        }
+
+        if (testDrive.mDeviceSerial == null) {
+            LOGGER.log(Level.SEVERE, "More than one devices connected. Please specify"
+                    + " with -s DEVICE_SERIAL");
+            return;
+        }
+
+        for (int i = arg_index; i < args.length; i++) {
             try {
                 int atomId = Integer.valueOf(args[i]);
                 if (Atom.getDescriptor().findFieldByNumber(atomId) == null) {
@@ -109,7 +137,7 @@
                 LOGGER.log(Level.SEVERE, "Failed to create valid config.");
                 return;
             }
-            remoteConfigPath = testDrive.pushConfig(config);
+            remoteConfigPath = testDrive.pushConfig(config, testDrive.mDeviceSerial);
             LOGGER.info("Pushed the following config to statsd:");
             LOGGER.info(config.toString());
             if (!hasPulledAtom(trackedAtoms)) {
@@ -120,17 +148,18 @@
             } else {
                 LOGGER.info("Now wait for 1.5 minutes ...");
                 Thread.sleep(15_000);
-                Utils.logAppBreadcrumb(0, 0, LOGGER);
+                Utils.logAppBreadcrumb(0, 0, LOGGER, testDrive.mDeviceSerial);
                 Thread.sleep(75_000);
             }
             testDrive.dumpMetrics();
         } catch (Exception e) {
             LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e);
         } finally {
-            testDrive.removeConfig();
+            testDrive.removeConfig(testDrive.mDeviceSerial);
             if (remoteConfigPath != null) {
                 try {
-                    Utils.runCommand(null, LOGGER, "adb", "shell", "rm", remoteConfigPath);
+                    Utils.runCommand(null, LOGGER,
+                            "adb", "-s", testDrive.mDeviceSerial, "shell", "rm", remoteConfigPath);
                 } catch (Exception e) {
                     LOGGER.log(Level.WARNING,
                             "Unable to remove remote config file: " + remoteConfigPath, e);
@@ -140,7 +169,8 @@
     }
 
     private void dumpMetrics() throws Exception {
-        ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER);
+        ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER,
+                mDeviceSerial);
         // We may get multiple reports. Take the last one.
         ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1);
         for (StatsLogReport statsLog : report.getMetricsList()) {
@@ -216,22 +246,24 @@
         return atomMatcherBuilder.build();
     }
 
-    private static String pushConfig(StatsdConfig config) throws IOException, InterruptedException {
+    private static String pushConfig(StatsdConfig config, String deviceSerial)
+            throws IOException, InterruptedException {
         File configFile = File.createTempFile("statsdconfig", ".config");
         configFile.deleteOnExit();
         Files.write(config.toByteArray(), configFile);
         String remotePath = "/data/local/tmp/" + configFile.getName();
-        Utils.runCommand(null, LOGGER, "adb", "push", configFile.getAbsolutePath(), remotePath);
-        Utils.runCommand(null, LOGGER,
-                "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
+        Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                "push", configFile.getAbsolutePath(), remotePath);
+        Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
                 String.valueOf(CONFIG_ID));
         return remotePath;
     }
 
-    private static void removeConfig() {
+    private static void removeConfig(String deviceSerial) {
         try {
-            Utils.runCommand(null, LOGGER,
-                    "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
+            Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                    "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
         } catch (Exception e) {
             LOGGER.log(Level.SEVERE, "Failed to remove config: " + e.getMessage());
         }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2531c89..b6d519a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -73,8 +73,8 @@
 import android.util.DisplayMetrics;
 import android.util.Singleton;
 import android.util.Size;
-import android.window.WindowContainerToken;
 import android.view.Surface;
+import android.window.WindowContainerToken;
 
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.procstats.ProcessStats;
@@ -3632,7 +3632,8 @@
      * Set custom state data for this process. It will be included in the record of
      * {@link ApplicationExitInfo} on the death of the current calling process; the new process
      * of the app can retrieve this state data by calling
-     * {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by
+     * {@link android.app.ApplicationExitInfo#getProcessStateSummary()
+     * ApplicationExitInfo.getProcessStateSummary()} on the record returned by
      * {@link #getHistoricalProcessExitReasons}.
      *
      * <p> This would be useful for the calling app to save its stateful data: if it's
@@ -3657,7 +3658,7 @@
         }
     }
 
-    /*
+    /**
      * @return Whether or not the low memory kill will be reported in
      * {@link #getHistoricalProcessExitReasons}.
      *
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 0ecc003..cfe0aff 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -90,7 +90,8 @@
      * {@link #REASON_SIGNALED} and {@link #getStatus} will return
      * the value {@link android.system.OsConstants#SIGKILL}.
      *
-     * Application should use {@link ActivityManager#isLowMemoryKillReportSupported} to check
+     * Application should use {@link android.app.ActivityManager#isLowMemoryKillReportSupported()
+     * ActivityManager.isLowMemoryKillReportSupported()} to check
      * if the device supports reporting {@link #REASON_LOW_MEMORY} or not.
      * </p>
      */
@@ -523,7 +524,7 @@
         return mReason;
     }
 
-    /*
+    /**
      * The exit status argument of exit() if the application calls it, or the signal
      * number if the application is signaled.
      */
@@ -538,7 +539,7 @@
         return mImportance;
     }
 
-    /*
+    /**
      * Last proportional set size of the memory that the process had used in kB.
      *
      * <p class="note">Note: This is the value from last sampling on the process,
@@ -562,7 +563,7 @@
 
     /**
      * The timestamp of the process's death, in milliseconds since the epoch,
-     * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}.
+     * as returned by {@link java.lang.System#currentTimeMillis() System.currentTimeMillis()}.
      */
     public @CurrentTimeMillisLong long getTimestamp() {
         return mTimestamp;
@@ -586,8 +587,9 @@
     }
 
     /**
-     * Return the state data set by calling {@link ActivityManager#setProcessStateSummary}
-     * from the process before its death.
+     * Return the state data set by calling
+     * {@link android.app.ActivityManager#setProcessStateSummary(byte[])
+     * ActivityManager.setProcessStateSummary(byte[])} from the process before its death.
      *
      * @return The process-customized data
      * @see ActivityManager#setProcessStateSummary(byte[])
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index a1ec27b..f883b60 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1610,7 +1610,10 @@
 
     @Override
     public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
-        Drawable badge = getProfileIconForDensity(user,
+        if (!hasUserBadge(user.getIdentifier())) {
+            return null;
+        }
+        Drawable badge = getDrawableForDensity(
                 getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
         if (badge != null) {
             badge.setTint(getUserBadgeColor(user));
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index dc8269f..b96b54a 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -34,6 +34,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -306,7 +307,8 @@
  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java
  *      bind}
  */
-public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
+public abstract class Service extends ContextWrapper implements ComponentCallbacks2,
+        ContentCaptureManager.ContentCaptureClient {
     private static final String TAG = "Service";
 
     /**
@@ -817,6 +819,14 @@
         writer.println("nothing to dump");
     }
 
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        if (newBase != null) {
+            newBase.setContentCaptureOptions(getContentCaptureOptions());
+        }
+    }
+
     // ------------------ Internal API ------------------
     
     /**
@@ -835,6 +845,8 @@
         mActivityManager = (IActivityManager)activityManager;
         mStartCompatibility = getApplicationInfo().targetSdkVersion
                 < Build.VERSION_CODES.ECLAIR;
+
+        setContentCaptureOptions(application.getContentCaptureOptions());
     }
 
     /**
@@ -849,6 +861,18 @@
         return mClassName;
     }
 
+    /** @hide */
+    @Override
+    public final ContentCaptureManager.ContentCaptureClient getContentCaptureClient() {
+        return this;
+    }
+
+    /** @hide */
+    @Override
+    public final ComponentName contentCaptureClientGetComponentName() {
+        return new ComponentName(this, mClassName);
+    }
+
     // set by the thread after the constructor and before onCreate(Bundle icicle) is called.
     @UnsupportedAppUsage
     private ActivityThread mThread = null;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 054e5e0..91a8572 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@
 import android.telephony.TelephonyRegistryManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
@@ -222,6 +223,9 @@
 public final class SystemServiceRegistry {
     private static final String TAG = "SystemServiceRegistry";
 
+    /** @hide */
+    public static boolean sEnableServiceNotFoundWtf = false;
+
     // Service registry information.
     // This information is never changed once static initialization has completed.
     private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
@@ -1364,8 +1368,30 @@
      * @hide
      */
     public static Object getSystemService(ContextImpl ctx, String name) {
-        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
-        return fetcher != null ? fetcher.getService(ctx) : null;
+        if (name == null) {
+            return null;
+        }
+        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
+        if (fetcher == null) {
+            if (sEnableServiceNotFoundWtf) {
+                Slog.wtf(TAG, "Unknown manager requested: " + name);
+            }
+            return null;
+        }
+
+        final Object ret = fetcher.getService(ctx);
+        if (sEnableServiceNotFoundWtf && ret == null) {
+            // Some services do return null in certain situations, so don't do WTF for them.
+            switch (name) {
+                case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
+                case Context.APP_PREDICTION_SERVICE:
+                case Context.INCREMENTAL_SERVICE:
+                    return null;
+            }
+            Slog.wtf(TAG, "Manager wrapper not available: " + name);
+            return null;
+        }
+        return ret;
     }
 
     /**
@@ -1373,7 +1399,15 @@
      * @hide
      */
     public static String getSystemServiceName(Class<?> serviceClass) {
-        return SYSTEM_SERVICE_NAMES.get(serviceClass);
+        if (serviceClass == null) {
+            return null;
+        }
+        final String serviceName = SYSTEM_SERVICE_NAMES.get(serviceClass);
+        if (sEnableServiceNotFoundWtf && serviceName == null) {
+            // This should be a caller bug.
+            Slog.wtf(TAG, "Unknown manager requested: " + serviceClass.getCanonicalName());
+        }
+        return serviceName;
     }
 
     /**
@@ -1683,7 +1717,9 @@
                         try {
                             cache.wait();
                         } catch (InterruptedException e) {
-                            Log.w(TAG, "getService() interrupted");
+                            // This shouldn't normally happen, but if someone interrupts the
+                            // thread, it will.
+                            Slog.wtf(TAG, "getService() interrupted");
                             Thread.currentThread().interrupt();
                             return null;
                         }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 7b45b72..ab86860 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -44,7 +44,7 @@
             "name": "CtsWindowManagerDeviceTestCases",
             "options": [
                 {
-                    "include-filter": "android.server.wm.ToastTest"
+                    "include-filter": "android.server.wm.ToastWindowTest"
                 }
             ],
             "file_patterns": ["INotificationManager\\.aidl"]
diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java
index 5b7e387..db833ec 100644
--- a/core/java/android/app/admin/DevicePolicyKeyguardService.java
+++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
@@ -28,14 +29,16 @@
 /**
  * Client interface for providing the SystemUI with secondary lockscreen information.
  *
- * <p>An implementation must be provided by the Profile Owner when
- * {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is set to true and the service must be
- * declared in the manifest as handling the action
+ * <p>An implementation must be provided by the default configured supervision app that is set as
+ * Profile Owner or Device Owner when {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is
+ * set to true and the service must be declared in the manifest as handling the action
  * {@link DevicePolicyManager#ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE}, otherwise the keyguard
  * will fail to bind to the service and continue to unlock.
  *
  * @see DevicePolicyManager#setSecondaryLockscreenEnabled
+ * @hide
  */
+@SystemApi
 public class DevicePolicyKeyguardService extends Service {
     private static final String TAG = "DevicePolicyKeyguardService";
     private IKeyguardCallback mCallback;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 10309a9..faf9ec6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2395,9 +2395,11 @@
     public static final int MAX_PASSWORD_LENGTH = 16;
 
     /**
-     * Service Action: Service implemented by a device owner or profile owner to provide a
-     * secondary lockscreen.
+     * Service Action: Service implemented by a device owner or profile owner supervision app to
+     * provide a secondary lockscreen.
+     * @hide
      */
+    @SystemApi
     public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE =
             "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
 
@@ -7001,6 +7003,22 @@
     }
 
     /**
+     * Returns the configured supervision app if it exists and is the device owner or policy owner.
+     * @hide
+     */
+    public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+            @NonNull UserHandle user) {
+        if (mService != null) {
+            try {
+                return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent(user);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * @hide
      * @return the human readable name of the organisation associated with this DPM or {@code null}
      *         if one is not set.
@@ -8637,11 +8655,16 @@
      * <p>Relevant interactions on the secondary lockscreen should be communicated back to the
      * keyguard via {@link IKeyguardCallback}, such as when the screen is ready to be dismissed.
      *
+     * <p>This API, and associated APIs, can only be called by the default supervision app when it
+     * is set as the device owner or profile owner.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param enabled Whether or not the lockscreen needs to be shown.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @see #isSecondaryLockscreenEnabled
+     * @hide
      **/
+    @SystemApi
     public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) {
         throwIfParentInstance("setSecondaryLockscreenEnabled");
         if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fc1eb0a..591a3f6 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -155,6 +155,7 @@
     boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle);
     ComponentName getProfileOwnerAsUser(int userHandle);
     ComponentName getProfileOwner(int userHandle);
+    ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(in UserHandle userHandle);
     String getProfileOwnerName(int userHandle);
     void setProfileEnabled(in ComponentName who);
     void setProfileName(in ComponentName who, String profileName);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 894ad55..be1817d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -322,7 +322,12 @@
     private String className;
     private int compatibleWidthLimitDp;
     private int descriptionRes;
-    private boolean enabled;
+
+    // Usually there's code to set this to true during parsing, but it's possible to install an APK
+    // targeting <R that doesn't contain an <application> tag. That code would be skipped and never
+    // assign this, so initialize this to true for those cases.
+    private boolean enabled = true;
+
     private boolean crossProfile;
     private int fullBackupContent;
     private int iconRes;
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index daf9a14..9ebc996 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksResourceLoaderTests"
+      "name": "CtsResourcesLoaderTests"
     }
   ]
 }
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]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index b52b437..a298c85 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -18,6 +18,7 @@
 
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -37,6 +38,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
@@ -52,7 +54,6 @@
 import java.lang.ref.WeakReference;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Implements the internal IInputMethod interface to convert incoming calls
@@ -90,12 +91,13 @@
      *
      * <p>This field must be set and cleared only from the binder thread(s), where the system
      * guarantees that {@link #bindInput(InputBinding)},
-     * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+     * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and
      * {@link #unbindInput()} are called with the same order as the original calls
      * in {@link com.android.server.inputmethod.InputMethodManagerService}.
      * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
      */
-    AtomicBoolean mIsUnbindIssued = null;
+    @Nullable
+    CancellationGroup mCancellationGroup = null;
 
     // NOTE: we should have a cache of these.
     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
@@ -187,11 +189,11 @@
                 final IBinder startInputToken = (IBinder) args.arg1;
                 final IInputContext inputContext = (IInputContext) args.arg2;
                 final EditorInfo info = (EditorInfo) args.arg3;
-                final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+                final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
                 SomeArgs moreArgs = (SomeArgs) args.arg5;
                 final InputConnection ic = inputContext != null
                         ? new InputConnectionWrapper(
-                                mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+                                mTarget, inputContext, moreArgs.argi3, cancellationGroup)
                         : null;
                 info.makeCompatible(mTargetSdkVersion);
                 inputMethod.dispatchStartInputWithToken(
@@ -295,15 +297,15 @@
     @BinderThread
     @Override
     public void bindInput(InputBinding binding) {
-        if (mIsUnbindIssued != null) {
+        if (mCancellationGroup != null) {
             Log.e(TAG, "bindInput must be paired with unbindInput.");
         }
-        mIsUnbindIssued = new AtomicBoolean();
+        mCancellationGroup = new CancellationGroup();
         // This IInputContext is guaranteed to implement all the methods.
         final int missingMethodFlags = 0;
         InputConnection ic = new InputConnectionWrapper(mTarget,
                 IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
-                mIsUnbindIssued);
+                mCancellationGroup);
         InputBinding nu = new InputBinding(ic, binding);
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
     }
@@ -311,10 +313,10 @@
     @BinderThread
     @Override
     public void unbindInput() {
-        if (mIsUnbindIssued != null) {
+        if (mCancellationGroup != null) {
             // Signal the flag then forget it.
-            mIsUnbindIssued.set(true);
-            mIsUnbindIssued = null;
+            mCancellationGroup.cancelAll();
+            mCancellationGroup = null;
         } else {
             Log.e(TAG, "unbindInput must be paired with bindInput.");
         }
@@ -326,16 +328,16 @@
     public void startInput(IBinder startInputToken, IInputContext inputContext,
             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
             EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
-        if (mIsUnbindIssued == null) {
+        if (mCancellationGroup == null) {
             Log.e(TAG, "startInput must be called after bindInput.");
-            mIsUnbindIssued = new AtomicBoolean();
+            mCancellationGroup = new CancellationGroup();
         }
         SomeArgs args = SomeArgs.obtain();
         args.argi1 = restarting ? 1 : 0;
         args.argi2 = shouldPreRenderIme ? 1 : 0;
         args.argi3 = missingMethods;
-        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
-                DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken,
+                inputContext, attribute, mCancellationGroup, args));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
index ef138a0..dbb669b 100644
--- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
+++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IMultiClientInputMethodSession;
+import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.view.IInputContext;
@@ -46,7 +47,6 @@
 import com.android.internal.view.InputConnectionWrapper;
 
 import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread.
@@ -80,19 +80,19 @@
     @Nullable
     InputEventReceiver mInputEventReceiver;
 
-    private final AtomicBoolean mFinished = new AtomicBoolean(false);
+    private final CancellationGroup mCancellationGroup = new CancellationGroup();
 
     IInputMethodSession.Stub createIInputMethodSession() {
         synchronized (mSessionLock) {
             return new InputMethodSessionImpl(
-                    mSessionLock, mCallbackImpl, mHandler, mFinished);
+                    mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
         }
     }
 
     IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() {
         synchronized (mSessionLock) {
             return new MultiClientInputMethodSessionImpl(
-                    mSessionLock, mCallbackImpl, mHandler, mFinished);
+                    mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
         }
     }
 
@@ -105,7 +105,7 @@
             mHandler = new Handler(looper, null, true);
             mReadChannel = readChannel;
             mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(),
-                    mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback);
+                    mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback);
         }
     }
 
@@ -139,16 +139,17 @@
     }
 
     private static final class ImeInputEventReceiver extends InputEventReceiver {
-        private final AtomicBoolean mFinished;
+        private final CancellationGroup mCancellationGroupOnFinishSession;
         private final KeyEvent.DispatcherState mDispatcherState;
         private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback;
         private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor;
 
-        ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished,
+        ImeInputEventReceiver(InputChannel readChannel, Looper looper,
+                CancellationGroup cancellationGroupOnFinishSession,
                 KeyEvent.DispatcherState dispatcherState,
                 MultiClientInputMethodServiceDelegate.ClientCallback callback) {
             super(readChannel, looper);
-            mFinished = finished;
+            mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
             mDispatcherState = dispatcherState;
             mClientCallback = callback;
             mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback);
@@ -156,7 +157,7 @@
 
         @Override
         public void onInputEvent(InputEvent event) {
-            if (mFinished.get()) {
+            if (mCancellationGroupOnFinishSession.isCanceled()) {
                 // The session has been finished.
                 finishInputEvent(event, false);
                 return;
@@ -187,14 +188,14 @@
         private CallbackImpl mCallbackImpl;
         @GuardedBy("mSessionLock")
         private Handler mHandler;
-        private final AtomicBoolean mSessionFinished;
+        private final CancellationGroup mCancellationGroupOnFinishSession;
 
         InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler,
-                AtomicBoolean sessionFinished) {
+                CancellationGroup cancellationGroupOnFinishSession) {
             mSessionLock = lock;
             mCallbackImpl = callback;
             mHandler = handler;
-            mSessionFinished = sessionFinished;
+            mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
         }
 
         @Override
@@ -272,7 +273,7 @@
                 if (mCallbackImpl == null || mHandler == null) {
                     return;
                 }
-                mSessionFinished.set(true);
+                mCancellationGroupOnFinishSession.cancelAll();
                 mHandler.sendMessage(PooledLambda.obtainMessage(
                         CallbackImpl::finishSession, mCallbackImpl));
                 mCallbackImpl = null;
@@ -311,14 +312,14 @@
         private CallbackImpl mCallbackImpl;
         @GuardedBy("mSessionLock")
         private Handler mHandler;
-        private final AtomicBoolean mSessionFinished;
+        private final CancellationGroup mCancellationGroupOnFinishSession;
 
         MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback,
-                Handler handler, AtomicBoolean sessionFinished) {
+                Handler handler, CancellationGroup cancellationGroupOnFinishSession) {
             mSessionLock = lock;
             mCallbackImpl = callback;
             mHandler = handler;
-            mSessionFinished = sessionFinished;
+            mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
         }
 
         @Override
@@ -335,7 +336,7 @@
                         new WeakReference<>(null);
                 args.arg1 = (inputContext == null) ? null
                         : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods,
-                                mSessionFinished);
+                                mCancellationGroupOnFinishSession);
                 args.arg2 = editorInfo;
                 args.argi1 = controlFlags;
                 args.argi2 = softInputMode;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 9ff7ebe..73c6b3d 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -169,6 +169,7 @@
             NET_CAPABILITY_OEM_PAID,
             NET_CAPABILITY_MCX,
             NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED,
     })
     public @interface NetCapability { }
 
@@ -336,8 +337,16 @@
     @SystemApi
     public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
 
+    /**
+     * This capability will be set for networks that are generally metered, but are currently
+     * unmetered, e.g., because the user is in a particular area. This capability can be changed at
+     * any time. When it is removed, applications are responsible for stopping any data transfer
+     * that should not occur on a metered network.
+     */
+    public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
+
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 
     /**
      * Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -353,7 +362,8 @@
             | (1 << NET_CAPABILITY_FOREGROUND)
             | (1 << NET_CAPABILITY_NOT_CONGESTED)
             | (1 << NET_CAPABILITY_NOT_SUSPENDED)
-            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY
+            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED));
 
     /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -424,6 +434,7 @@
      */
     private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
             (1 << NET_CAPABILITY_NOT_METERED)
+            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
             | (1 << NET_CAPABILITY_NOT_RESTRICTED)
             | (1 << NET_CAPABILITY_NOT_VPN)
             | (1 << NET_CAPABILITY_NOT_ROAMING)
@@ -1864,6 +1875,7 @@
             case NET_CAPABILITY_OEM_PAID:             return "OEM_PAID";
             case NET_CAPABILITY_MCX:                  return "MCX";
             case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
+            case NET_CAPABILITY_TEMPORARILY_NOT_METERED:    return "TEMPORARILY_NOT_METERED";
             default:                                  return Integer.toString(capability);
         }
     }
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 327bca2..2e00c0c 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1274,6 +1274,8 @@
 
             out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
         } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
+            enforceReadPermissionInner(documentUri, getCallingPackage(),
+                    getCallingAttributionTag(), null);
             return getDocumentMetadata(documentId);
         } else {
             throw new UnsupportedOperationException("Method not supported " + method);
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 4b3afba..e8d3459 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -335,7 +335,17 @@
 
     /** @hide Just for debugging; not internationalized. */
     public static String formatUptime(long time) {
-        final long diff = time - SystemClock.uptimeMillis();
+        return formatTime(time, SystemClock.uptimeMillis());
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static String formatRealtime(long time) {
+        return formatTime(time, SystemClock.elapsedRealtime());
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static String formatTime(long time, long referenceTime) {
+        long diff = time - referenceTime;
         if (diff > 0) {
             return time + " (in " + diff + " ms)";
         }
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 6784cf7..dbbe4b6 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -170,10 +170,15 @@
         }
         if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + mServedView);
 
+        // We don't need to track the next served view when the view lost focus here because:
+        // 1) The current view focus may be cleared temporary when in touch mode, closing input
+        //    at this moment isn't the right way.
+        // 2) We only care about the served view change when it focused, since changing input
+        //    connection when the focus target changed is reasonable.
+        // 3) Setting the next served view as null when no more served view should be handled in
+        //    other special events (e.g. view detached from window or the window dismissed).
         if (hasFocus) {
             mNextServedView = view;
-        } else if (view == mServedView) {
-            mNextServedView = null;
         }
         mViewRootImpl.dispatchCheckFocus();
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 35f955f..b5e8dd8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1747,16 +1747,13 @@
                 || !mBlastSurfaceControl.isValid()) {
             return null;
         }
+
         if (mBlastBufferQueue == null) {
             mBlastBufferQueue = new BLASTBufferQueue(
                 mBlastSurfaceControl, width, height);
         }
         mBlastBufferQueue.update(mBlastSurfaceControl, width, height);
 
-        mTransaction.show(mBlastSurfaceControl)
-            .reparent(mBlastSurfaceControl, mSurfaceControl)
-            .apply();
-
         return mBlastBufferQueue.getSurface();
     }
 
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/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index d0b8dbc..f089f48 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -13,7 +13,7 @@
       "name": "CtsWindowManagerDeviceTestCases",
       "options": [
         {
-          "include-filter": "android.server.wm.ToastTest"
+          "include-filter": "android.server.wm.ToastWindowTest"
         }
       ],
       "file_patterns": ["Toast\\.java"]
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 4f14539..08b3293 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -117,7 +117,6 @@
     private final Binder mToken;
     private final Context mContext;
     private final Handler mHandler;
-    private final ToastPresenter mPresenter;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     final TN mTN;
     @UnsupportedAppUsage
@@ -165,8 +164,8 @@
         looper = getLooper(looper);
         mHandler = new Handler(looper);
         mCallbacks = new ArrayList<>();
-        mPresenter = new ToastPresenter(context, AccessibilityManager.getInstance(context));
-        mTN = new TN(mPresenter, context.getPackageName(), mToken, mCallbacks, looper);
+        mTN = new TN(context, context.getPackageName(), mToken,
+                mCallbacks, looper);
         mTN.mY = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.toast_y_offset);
         mTN.mGravity = context.getResources().getInteger(
@@ -496,7 +495,7 @@
             return result;
         } else {
             Toast result = new Toast(context, looper);
-            View v = result.mPresenter.getTextToastView(text);
+            View v = ToastPresenter.getTextToastView(context, text);
             result.mNextView = v;
             result.mDuration = duration;
 
@@ -565,13 +564,14 @@
         if (sService != null) {
             return sService;
         }
-        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
+        sService = INotificationManager.Stub.asInterface(
+                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
         return sService;
     }
 
     private static class TN extends ITransientNotification.Stub {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        private final WindowManager.LayoutParams mParams;
 
         private static final int SHOW = 0;
         private static final int HIDE = 1;
@@ -608,9 +608,13 @@
          * The parameter {@code callbacks} is not copied and is accessed with itself as its own
          * lock.
          */
-        TN(ToastPresenter presenter, String packageName, Binder token, List<Callback> callbacks,
+        TN(Context context, String packageName, Binder token, List<Callback> callbacks,
                 @Nullable Looper looper) {
-            mPresenter = presenter;
+            WindowManager windowManager = context.getSystemService(WindowManager.class);
+            AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
+            mPresenter = new ToastPresenter(context, windowManager, accessibilityManager,
+                    getService(), packageName);
+            mParams = mPresenter.getLayoutParams();
             mPackageName = packageName;
             mToken = token;
             mCallbacks = callbacks;
@@ -645,8 +649,6 @@
                     }
                 }
             };
-
-            presenter.startLayoutParams(mParams, packageName);
         }
 
         private List<Callback> getCallbacks() {
@@ -691,31 +693,9 @@
                 // remove the old view if necessary
                 handleHide();
                 mView = mNextView;
-                Context context = mView.getContext().getApplicationContext();
-                if (context == null) {
-                    context = mView.getContext();
-                }
-                mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-                mPresenter.adjustLayoutParams(mParams, windowToken, mDuration, mGravity, mX, mY,
-                        mHorizontalMargin, mVerticalMargin);
-                if (mView.getParent() != null) {
-                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
-                    mWM.removeView(mView);
-                }
-                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
-                // Since the notification manager service cancels the token right
-                // after it notifies us to cancel the toast there is an inherent
-                // race and we may attempt to add a window after the token has been
-                // invalidated. Let us hedge against that.
-                try {
-                    mWM.addView(mView, mParams);
-                    mPresenter.trySendAccessibilityEvent(mView, mPackageName);
-                    for (Callback callback : getCallbacks()) {
-                        callback.onToastShown();
-                    }
-                } catch (WindowManager.BadTokenException e) {
-                    /* ignore */
-                }
+                mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
+                        mHorizontalMargin, mVerticalMargin,
+                        new CallbackBinder(getCallbacks(), mHandler));
             }
         }
 
@@ -723,25 +703,9 @@
         public void handleHide() {
             if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
             if (mView != null) {
-                // note: checking parent() just to make sure the view has
-                // been added...  i have seen cases where we get here when
-                // the view isn't yet added, so let's try not to crash.
-                if (mView.getParent() != null) {
-                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
-                    mWM.removeViewImmediate(mView);
-                }
-
-
-                // Now that we've removed the view it's safe for the server to release
-                // the resources.
-                try {
-                    getService().finishToken(mPackageName, mToken);
-                } catch (RemoteException e) {
-                }
-
-                for (Callback callback : getCallbacks()) {
-                    callback.onToastHidden();
-                }
+                checkState(mView == mPresenter.getView(),
+                        "Trying to hide toast view different than the last one displayed");
+                mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler));
                 mView = null;
             }
         }
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 0447b6b..e9d4aa6 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -16,11 +16,18 @@
 
 package android.widget;
 
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.Nullable;
+import android.app.INotificationManager;
+import android.app.ITransientNotificationCallback;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -37,41 +44,94 @@
  * @hide
  */
 public class ToastPresenter {
+    private static final String TAG = "ToastPresenter";
+    private static final String WINDOW_TITLE = "Toast";
     private static final long SHORT_DURATION_TIMEOUT = 4000;
     private static final long LONG_DURATION_TIMEOUT = 7000;
 
+    /**
+     * Returns the default text toast view for message {@code text}.
+     */
+    public static View getTextToastView(Context context, CharSequence text) {
+        View view = LayoutInflater.from(context).inflate(
+                R.layout.transient_notification, null);
+        TextView textView = view.findViewById(com.android.internal.R.id.message);
+        textView.setText(text);
+        return view;
+    }
+
     private final Context mContext;
     private final Resources mResources;
+    private final WindowManager mWindowManager;
     private final AccessibilityManager mAccessibilityManager;
+    private final INotificationManager mNotificationManager;
+    private final String mPackageName;
+    private final WindowManager.LayoutParams mParams;
+    @Nullable private View mView;
+    @Nullable private IBinder mToken;
 
-    public ToastPresenter(Context context, AccessibilityManager accessibilityManager) {
+    public ToastPresenter(Context context, WindowManager windowManager,
+            AccessibilityManager accessibilityManager,
+            INotificationManager notificationManager, String packageName) {
         mContext = context;
         mResources = context.getResources();
+        mWindowManager = windowManager;
         mAccessibilityManager = accessibilityManager;
+        mNotificationManager = notificationManager;
+        mPackageName = packageName;
+        mParams = createLayoutParams();
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public WindowManager.LayoutParams getLayoutParams() {
+        return mParams;
     }
 
     /**
-     * Initializes {@code params} with default values for toasts.
+     * Returns the {@link View} being shown at the moment or {@code null} if no toast is being
+     * displayed.
      */
-    public void startLayoutParams(WindowManager.LayoutParams params, String packageName) {
+    @Nullable
+    public View getView() {
+        return mView;
+    }
+
+    /**
+     * Returns the {@link IBinder} token used to display the toast or {@code null} if there is no
+     * toast being shown at the moment.
+     */
+    @Nullable
+    public IBinder getToken() {
+        return mToken;
+    }
+
+    /**
+     * Creates {@link WindowManager.LayoutParams} with default values for toasts.
+     */
+    private WindowManager.LayoutParams createLayoutParams() {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
         params.height = WindowManager.LayoutParams.WRAP_CONTENT;
         params.width = WindowManager.LayoutParams.WRAP_CONTENT;
         params.format = PixelFormat.TRANSLUCENT;
         params.windowAnimations = R.style.Animation_Toast;
         params.type = WindowManager.LayoutParams.TYPE_TOAST;
         params.setFitInsetsIgnoringVisibility(true);
-        params.setTitle("Toast");
+        params.setTitle(WINDOW_TITLE);
         params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-        setShowForAllUsersIfApplicable(params, packageName);
+        setShowForAllUsersIfApplicable(params, mPackageName);
+        return params;
     }
 
     /**
      * Customizes {@code params} according to other parameters, ready to be passed to {@link
      * WindowManager#addView(View, ViewGroup.LayoutParams)}.
      */
-    public void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
+    private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
             int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
             float verticalMargin) {
         Configuration config = mResources.getConfiguration();
@@ -97,7 +157,7 @@
      * Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
      * packageName} is a cross-user package.
      *
-     * Implementation note:
+     * <p>Implementation note:
      *     This code is safe to be executed in SystemUI and the app's process:
      *         <li>SystemUI: It's running on a trusted domain so apps can't tamper with it. SystemUI
      *             has the permission INTERNAL_SYSTEM_WINDOW needed by the flag, so SystemUI can add
@@ -120,14 +180,66 @@
     }
 
     /**
-     * Returns the default text toast view for message {@code text}.
+     * Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
      */
-    public View getTextToastView(CharSequence text) {
-        View view = LayoutInflater.from(mContext).inflate(
-                R.layout.transient_notification, null);
-        TextView textView = view.findViewById(com.android.internal.R.id.message);
-        textView.setText(text);
-        return view;
+    public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
+            int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
+            @Nullable ITransientNotificationCallback callback) {
+        checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");
+        mView = view;
+        mToken = token;
+
+        adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
+                horizontalMargin, verticalMargin);
+        if (mView.getParent() != null) {
+            mWindowManager.removeView(mView);
+        }
+        try {
+            mWindowManager.addView(mView, mParams);
+        } catch (WindowManager.BadTokenException e) {
+            // Since the notification manager service cancels the token right after it notifies us
+            // to cancel the toast there is an inherent race and we may attempt to add a window
+            // after the token has been invalidated. Let us hedge against that.
+            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+            return;
+        }
+        trySendAccessibilityEvent(mView, mPackageName);
+        if (callback != null) {
+            try {
+                callback.onToastShown();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
+            }
+        }
+    }
+
+    /**
+     * Hides toast that was shown using {@link #show(View, IBinder, IBinder, int,
+     * int, int, int, float, float, ITransientNotificationCallback)}.
+     *
+     * <p>This method has to be called on the same thread on which {@link #show(View, IBinder,
+     * IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called.
+     */
+    public void hide(@Nullable ITransientNotificationCallback callback) {
+        checkState(mView != null, "No toast to hide.");
+
+        if (mView.getParent() != null) {
+            mWindowManager.removeViewImmediate(mView);
+        }
+        try {
+            mNotificationManager.finishToken(mPackageName, mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
+        }
+        if (callback != null) {
+            try {
+                callback.onToastHidden();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
+            }
+        }
+        mView = null;
+        mToken = null;
     }
 
     /**
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index dca682e..c82ab6c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -124,6 +124,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.GridLayoutManager;
 import com.android.internal.widget.RecyclerView;
 import com.android.internal.widget.ResolverDrawerLayout;
@@ -178,7 +179,7 @@
     private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
     // TODO(b/123088566) Share these in a better way.
     private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
-    public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
+    public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
     private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
     public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
 
@@ -194,6 +195,14 @@
     public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
     public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
 
+    public static final int SELECTION_TYPE_SERVICE = 1;
+    public static final int SELECTION_TYPE_APP = 2;
+    public static final int SELECTION_TYPE_STANDARD = 3;
+    public static final int SELECTION_TYPE_COPY = 4;
+
+    // statsd logger wrapper
+    protected ChooserActivityLogger mChooserActivityLogger;
+
     private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
 
     @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
@@ -226,7 +235,7 @@
     private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
             DeviceConfig.NAMESPACE_SYSTEMUI,
             SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
-            false);
+            true);
 
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
@@ -270,9 +279,9 @@
 
     // Starting at 1 since 0 is considered "undefined" for some of the database transformations
     // of tron logs.
-    private static final int CONTENT_PREVIEW_IMAGE = 1;
-    private static final int CONTENT_PREVIEW_FILE = 2;
-    private static final int CONTENT_PREVIEW_TEXT = 3;
+    protected static final int CONTENT_PREVIEW_IMAGE = 1;
+    protected static final int CONTENT_PREVIEW_FILE = 2;
+    protected static final int CONTENT_PREVIEW_TEXT = 3;
     protected MetricsLogger mMetricsLogger;
 
     private ContentPreviewCoordinator mPreviewCoord;
@@ -500,6 +509,9 @@
 
                 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
                     mMinTimeoutPassed = true;
+                    if (!mServiceConnections.isEmpty()) {
+                        getChooserActivityLogger().logSharesheetDirectLoadTimeout();
+                    }
                     unbindRemainingServices();
                     maybeStopServiceRequestTimer();
                     break;
@@ -533,6 +545,7 @@
                     logDirectShareTargetReceived(
                             MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
                     sendVoiceChoicesIfNeeded();
+                    getChooserActivityLogger().logSharesheetDirectLoadComplete();
                     break;
 
                 default:
@@ -544,6 +557,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         final long intentReceivedTime = System.currentTimeMillis();
+        getChooserActivityLogger().logSharesheetTriggered();
         // This is the only place this value is being set. Effectively final.
         mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
 
@@ -707,6 +721,8 @@
                                 incrementNumSheetExpansions();
                                 mWrittenOnce = true;
                             }
+                            getChooserActivityLogger()
+                                    .logSharesheetExpansionChanged(isCollapsed);
                         }
                     });
         }
@@ -715,6 +731,16 @@
             Log.d(TAG, "System Time Cost is " + systemCost);
         }
 
+        getChooserActivityLogger().logShareStarted(
+                FrameworkStatsLog.SHARESHEET_STARTED,
+                getReferrerPackageName(),
+                target.getType(),
+                initialIntents == null ? 0 : initialIntents.length,
+                mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
+                isWorkProfile(),
+                findPreferredContentPreview(getTargetIntent(), getContentResolver()),
+                target.getAction()
+        );
         mDirectShareShortcutInfoCache = new HashMap<>();
     }
 
@@ -969,6 +995,10 @@
             LogMaker targetLogMaker = new LogMaker(
                     MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
             getMetricsLogger().write(targetLogMaker);
+            getChooserActivityLogger().logShareTargetSelected(
+                    SELECTION_TYPE_COPY,
+                    "",
+                    -1);
 
             finish();
         }
@@ -1644,18 +1674,33 @@
                     if (mCallerChooserTargets != null) {
                         numCallerProvided = mCallerChooserTargets.length;
                     }
+                    getChooserActivityLogger().logShareTargetSelected(
+                            SELECTION_TYPE_SERVICE,
+                            targetInfo.getResolveInfo().activityInfo.processName,
+                            value
+                    );
                     break;
                 case ChooserListAdapter.TARGET_CALLER:
                 case ChooserListAdapter.TARGET_STANDARD:
                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
                     value -= currentListAdapter.getSelectableServiceTargetCount();
                     numCallerProvided = currentListAdapter.getCallerTargetCount();
+                    getChooserActivityLogger().logShareTargetSelected(
+                            SELECTION_TYPE_APP,
+                            targetInfo.getResolveInfo().activityInfo.processName,
+                            value
+                    );
                     break;
                 case ChooserListAdapter.TARGET_STANDARD_AZ:
                     // A-Z targets are unranked standard targets; we use -1 to mark that they
                     // are from the alphabetical pool.
                     value = -1;
                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
+                    getChooserActivityLogger().logShareTargetSelected(
+                            SELECTION_TYPE_STANDARD,
+                            targetInfo.getResolveInfo().activityInfo.processName,
+                            value
+                    );
                     break;
             }
 
@@ -2131,7 +2176,7 @@
         if (appTarget != null) {
             directShareAppPredictor.notifyAppTargetEvent(
                     new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
-                        .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
+                        .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
                         .build());
         }
     }
@@ -2290,6 +2335,13 @@
         return mMetricsLogger;
     }
 
+    protected ChooserActivityLogger getChooserActivityLogger() {
+        if (mChooserActivityLogger == null) {
+            mChooserActivityLogger = new ChooserActivityLoggerImpl();
+        }
+        return mChooserActivityLogger;
+    }
+
     public class ChooserListController extends ResolverListController {
         public ChooserListController(Context context,
                 PackageManager pm,
@@ -2601,6 +2653,7 @@
 
         // don't support direct share on low ram devices
         if (ActivityManager.isLowRamDeviceStatic()) {
+            getChooserActivityLogger().logSharesheetAppLoadComplete();
             return;
         }
 
@@ -2619,6 +2672,8 @@
 
             queryTargetServices(chooserListAdapter);
         }
+
+        getChooserActivityLogger().logSharesheetAppLoadComplete();
     }
 
     private void setupScrollListener() {
@@ -3777,4 +3832,9 @@
             canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
         }
     }
+
+    @Override
+    protected void maybeLogProfileChange() {
+        getChooserActivityLogger().logShareheetProfileChanged();
+    }
 }
diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java
new file mode 100644
index 0000000..dc48244
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivityLogger.java
@@ -0,0 +1,215 @@
+/*
+ * 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.internal.app;
+
+import android.content.Intent;
+import android.provider.MediaStore;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Interface for writing Sharesheet atoms to statsd log.
+ * @hide
+ */
+public interface ChooserActivityLogger {
+    /** Logs a UiEventReported event for the system sharesheet completing initial start-up. */
+    void logShareStarted(int eventId, String packageName, String mimeType, int appProvidedDirect,
+            int appProvidedApp, boolean isWorkprofile, int previewType, String intent);
+
+    /** Logs a UiEventReported event for the system sharesheet when the user selects a target. */
+    void logShareTargetSelected(int targetType, String packageName, int positionPicked);
+
+    /** Logs a UiEventReported event for the system sharesheet being triggered by the user. */
+    default void logSharesheetTriggered() {
+        log(SharesheetStandardEvent.SHARESHEET_TRIGGERED, getInstanceId());
+    }
+
+    /** Logs a UiEventReported event for the system sharesheet completing loading app targets. */
+    default void logSharesheetAppLoadComplete() {
+        log(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE, getInstanceId());
+    }
+
+    /**
+     * Logs a UiEventReported event for the system sharesheet completing loading service targets.
+     */
+    default void logSharesheetDirectLoadComplete() {
+        log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE, getInstanceId());
+    }
+
+    /**
+     * Logs a UiEventReported event for the system sharesheet timing out loading service targets.
+     */
+    default void logSharesheetDirectLoadTimeout() {
+        log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT, getInstanceId());
+    }
+
+    /**
+     * Logs a UiEventReported event for the system sharesheet switching
+     * between work and main profile.
+     */
+    default void logShareheetProfileChanged() {
+        log(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED, getInstanceId());
+    }
+
+    /** Logs a UiEventReported event for the system sharesheet getting expanded or collapsed. */
+    default void logSharesheetExpansionChanged(boolean isCollapsed) {
+        log(isCollapsed ? SharesheetStandardEvent.SHARESHEET_COLLAPSED :
+                SharesheetStandardEvent.SHARESHEET_EXPANDED, getInstanceId());
+    }
+
+    /**
+     * Logs a UiEventReported event for a given share activity
+     * @param event
+     * @param instanceId
+     */
+    void log(UiEventLogger.UiEventEnum event, InstanceId instanceId);
+
+    /**
+     *
+     * @return
+     */
+    InstanceId getInstanceId();
+
+    /**
+     * The UiEvent enums that this class can log.
+     */
+    enum SharesheetStartedEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Basic system Sharesheet has started and is visible.")
+        SHARE_STARTED(228);
+
+        private final int mId;
+        SharesheetStartedEvent(int id) {
+            mId = id;
+        }
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
+    /**
+     * The UiEvent enums that this class can log.
+     */
+    enum SharesheetTargetSelectedEvent implements UiEventLogger.UiEventEnum {
+        INVALID(0),
+        @UiEvent(doc = "User selected a service target.")
+        SHARESHEET_SERVICE_TARGET_SELECTED(232),
+        @UiEvent(doc = "User selected an app target.")
+        SHARESHEET_APP_TARGET_SELECTED(233),
+        @UiEvent(doc = "User selected a standard target.")
+        SHARESHEET_STANDARD_TARGET_SELECTED(234),
+        @UiEvent(doc = "User selected the copy target.")
+        SHARESHEET_COPY_TARGET_SELECTED(235);
+
+        private final int mId;
+        SharesheetTargetSelectedEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+
+        public static SharesheetTargetSelectedEvent fromTargetType(int targetType) {
+            switch(targetType) {
+                case ChooserActivity.SELECTION_TYPE_SERVICE:
+                    return SHARESHEET_SERVICE_TARGET_SELECTED;
+                case ChooserActivity.SELECTION_TYPE_APP:
+                    return SHARESHEET_APP_TARGET_SELECTED;
+                case ChooserActivity.SELECTION_TYPE_STANDARD:
+                    return SHARESHEET_STANDARD_TARGET_SELECTED;
+                case ChooserActivity.SELECTION_TYPE_COPY:
+                    return SHARESHEET_COPY_TARGET_SELECTED;
+                default:
+                    return INVALID;
+            }
+        }
+    }
+
+    /**
+     * The UiEvent enums that this class can log.
+     */
+    enum SharesheetStandardEvent implements UiEventLogger.UiEventEnum {
+        INVALID(0),
+        @UiEvent(doc = "User clicked share.")
+        SHARESHEET_TRIGGERED(227),
+        @UiEvent(doc = "User changed from work to personal profile or vice versa.")
+        SHARESHEET_PROFILE_CHANGED(229),
+        @UiEvent(doc = "User expanded target list.")
+        SHARESHEET_EXPANDED(230),
+        @UiEvent(doc = "User collapsed target list.")
+        SHARESHEET_COLLAPSED(231),
+        @UiEvent(doc = "Sharesheet app targets is fully populated.")
+        SHARESHEET_APP_LOAD_COMPLETE(322),
+        @UiEvent(doc = "Sharesheet direct targets is fully populated.")
+        SHARESHEET_DIRECT_LOAD_COMPLETE(323),
+        @UiEvent(doc = "Sharesheet direct targets timed out.")
+        SHARESHEET_DIRECT_LOAD_TIMEOUT(324);
+
+        private final int mId;
+        SharesheetStandardEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
+
+    /**
+     * Returns the enum used in sharesheet started atom to indicate what preview type was used.
+     */
+    default int typeFromPreviewInt(int previewType) {
+        switch(previewType) {
+            case ChooserActivity.CONTENT_PREVIEW_IMAGE:
+                return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_IMAGE;
+            case ChooserActivity.CONTENT_PREVIEW_FILE:
+                return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE;
+            case ChooserActivity.CONTENT_PREVIEW_TEXT:
+            default:
+                return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TEXT;
+        }
+    }
+
+    /**
+     * Returns the enum used in sharesheet started atom to indicate what intent triggers the
+     * ChooserActivity.
+     */
+    default int typeFromIntentString(String intent) {
+        switch (intent) {
+            case Intent.ACTION_VIEW:
+                return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_VIEW;
+            case Intent.ACTION_EDIT:
+                return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_EDIT;
+            case Intent.ACTION_SEND:
+                return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND;
+            case Intent.ACTION_SENDTO:
+                return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO;
+            case Intent.ACTION_SEND_MULTIPLE:
+                return FrameworkStatsLog
+                        .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND_MULTIPLE;
+            case MediaStore.ACTION_IMAGE_CAPTURE:
+                return FrameworkStatsLog
+                        .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_IMAGE_CAPTURE;
+            case Intent.ACTION_MAIN:
+                return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_MAIN;
+            default:
+                return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
new file mode 100644
index 0000000..48bdba3
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
@@ -0,0 +1,82 @@
+/*
+ * 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.internal.app;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Standard implementation of ChooserActivityLogger interface.
+ * @hide
+ */
+public class ChooserActivityLoggerImpl implements ChooserActivityLogger {
+    private static final int SHARESHEET_INSTANCE_ID_MAX = (1 << 13);
+
+    private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+    // A small per-notification ID, used for statsd logging.
+    private InstanceId mInstanceId;
+    private static InstanceIdSequence sInstanceIdSequence;
+
+    @Override
+    public void logShareStarted(int eventId, String packageName, String mimeType,
+            int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+            String intent) {
+        FrameworkStatsLog.write(FrameworkStatsLog.SHARESHEET_STARTED,
+                /* event_id = 1 */ SharesheetStartedEvent.SHARE_STARTED.getId(),
+                /* package_name = 2 */ packageName,
+                /* instance_id = 3 */ getInstanceId().getId(),
+                /* mime_type = 4 */ mimeType,
+                /* num_app_provided_direct_targets = 5 */ appProvidedDirect,
+                /* num_app_provided_app_targets = 6 */ appProvidedApp,
+                /* is_workprofile = 7 */ isWorkprofile,
+                /* previewType = 8 */ typeFromPreviewInt(previewType),
+                /* intentType = 9 */ typeFromIntentString(intent));
+    }
+
+    @Override
+    public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+        FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED,
+                /* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(),
+                /* package_name = 2 */ packageName,
+                /* instance_id = 3 */ getInstanceId().getId(),
+                /* position_picked = 4 */ positionPicked);
+    }
+
+    @Override
+    public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
+        mUiEventLogger.logWithInstanceId(
+                event,
+                0,
+                null,
+                instanceId);
+    }
+
+    @Override
+    public InstanceId getInstanceId() {
+        if (mInstanceId == null) {
+            if (sInstanceIdSequence == null) {
+                sInstanceIdSequence = new InstanceIdSequence(SHARESHEET_INSTANCE_ID_MAX);
+            }
+            mInstanceId = sInstanceIdSequence.newInstanceId();
+        }
+        return mInstanceId;
+    }
+
+}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 0ea855a..0d90bbf 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -64,7 +64,7 @@
     private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
             DeviceConfig.NAMESPACE_SYSTEMUI,
             SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
-            false);
+            true);
 
     private boolean mEnableStackedApps = true;
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f088ab3..35253b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1578,6 +1578,7 @@
                 viewPager.setCurrentItem(1);
             }
             setupViewVisibilities();
+            maybeLogProfileChange();
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
                     .setInt(viewPager.getCurrentItem())
@@ -1998,4 +1999,6 @@
             }
         }
     }
+
+    protected void maybeLogProfileChange() {}
 }
diff --git a/core/java/com/android/internal/inputmethod/CancellationGroup.java b/core/java/com/android/internal/inputmethod/CancellationGroup.java
new file mode 100644
index 0000000..09c9d12
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/CancellationGroup.java
@@ -0,0 +1,348 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A utility class, which works as both a factory class of completable objects and a cancellation
+ * signal to cancel all the completable objects created by this object.
+ */
+public final class CancellationGroup {
+    private final Object mLock = new Object();
+
+    /**
+     * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to
+     * completable objects.
+     *
+     * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p>
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    private ArrayList<CountDownLatch> mLatchList = null;
+
+    @GuardedBy("mLock")
+    private boolean mCanceled = false;
+
+    /**
+     * An inner class to consolidate completable object types supported by
+     * {@link CancellationGroup}.
+     */
+    public static final class Completable {
+
+        /**
+         * Not intended to be instantiated.
+         */
+        private Completable() {
+        }
+
+        /**
+         * Base class of all the completable types supported by {@link CancellationGroup}.
+         */
+        protected static class ValueBase {
+            /**
+             * {@link CountDownLatch} to be signaled to unblock {@link #await(int, TimeUnit)}.
+             */
+            private final CountDownLatch mLatch = new CountDownLatch(1);
+
+            /**
+             * {@link CancellationGroup} to which this completable object belongs.
+             */
+            @NonNull
+            private final CancellationGroup mParentGroup;
+
+            /**
+             * Lock {@link Object} to guard complete operations within this class.
+             */
+            protected final Object mValueLock = new Object();
+
+            /**
+             * {@code true} after {@link #onComplete()} gets called.
+             */
+            @GuardedBy("mValueLock")
+            protected boolean mHasValue = false;
+
+            /**
+             * Base constructor.
+             *
+             * @param parentGroup {@link CancellationGroup} to which this completable object
+             *                    belongs.
+             */
+            protected ValueBase(@NonNull CancellationGroup parentGroup) {
+                mParentGroup = parentGroup;
+            }
+
+            /**
+             * @return {@link true} if {@link #onComplete()} gets called already.
+             */
+            @AnyThread
+            public boolean hasValue() {
+                synchronized (mValueLock) {
+                    return mHasValue;
+                }
+            }
+
+            /**
+             * Called by subclasses to signale {@link #mLatch}.
+             */
+            @AnyThread
+            protected void onComplete() {
+                mLatch.countDown();
+            }
+
+            /**
+             * Blocks the calling thread until at least one of the following conditions is met.
+             *
+             * <p>
+             *     <ol>
+             *         <li>This object becomes ready to return the value.</li>
+             *         <li>{@link CancellationGroup#cancelAll()} gets called.</li>
+             *         <li>The given timeout period has passed.</li>
+             *     </ol>
+             * </p>
+             *
+             * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}.
+             * Note that the return value of {@link #hasValue()} can change from {@code false} to
+             * {@code true} at any time, even after this methods finishes with returning
+             * {@code true}.</p>
+             *
+             * @param timeout length of the timeout.
+             * @param timeUnit unit of {@code timeout}.
+             * @return {@code false} if and only if the given timeout period has passed. Otherwise
+             *         {@code true}.
+             */
+            @AnyThread
+            public boolean await(int timeout, @NonNull TimeUnit timeUnit) {
+                if (!mParentGroup.registerLatch(mLatch)) {
+                    // Already canceled when this method gets called.
+                    return false;
+                }
+                try {
+                    return mLatch.await(timeout, timeUnit);
+                } catch (InterruptedException e) {
+                    return true;
+                } finally {
+                    mParentGroup.unregisterLatch(mLatch);
+                }
+            }
+        }
+
+        /**
+         * Completable object of integer primitive.
+         */
+        public static final class Int extends ValueBase {
+            @GuardedBy("mValueLock")
+            private int mValue = 0;
+
+            private Int(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+
+            /**
+             * Notify when a value is set to this completable object.
+             *
+             * @param value value to be set.
+             */
+            @AnyThread
+            void onComplete(int value) {
+                synchronized (mValueLock) {
+                    if (mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "onComplete() cannot be called multiple times");
+                    }
+                    mValue = value;
+                    mHasValue = true;
+                }
+                onComplete();
+            }
+
+            /**
+             * @return value associated with this object.
+             * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+             *                                       {@code false}.
+             */
+            @AnyThread
+            public int getValue() {
+                synchronized (mValueLock) {
+                    if (!mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "getValue() is allowed only if hasValue() returns true");
+                    }
+                    return mValue;
+                }
+            }
+        }
+
+        /**
+         * Base class of completable object types.
+         *
+         * @param <T> type associated with this completable object.
+         */
+        public static class Values<T> extends ValueBase {
+            @GuardedBy("mValueLock")
+            @Nullable
+            private T mValue = null;
+
+            protected Values(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+
+            /**
+             * Notify when a value is set to this completable value object.
+             *
+             * @param value value to be set.
+             */
+            @AnyThread
+            void onComplete(@Nullable T value) {
+                synchronized (mValueLock) {
+                    if (mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "onComplete() cannot be called multiple times");
+                    }
+                    mValue = value;
+                    mHasValue = true;
+                }
+                onComplete();
+            }
+
+            /**
+             * @return value associated with this object.
+             * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+             *                                       {@code false}.
+             */
+            @AnyThread
+            @Nullable
+            public T getValue() {
+                synchronized (mValueLock) {
+                    if (!mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "getValue() is allowed only if hasValue() returns true");
+                    }
+                    return mValue;
+                }
+            }
+        }
+
+        /**
+         * Completable object of {@link java.lang.CharSequence}.
+         */
+        public static final class CharSequence extends Values<java.lang.CharSequence> {
+            private CharSequence(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+        }
+
+        /**
+         * Completable object of {@link android.view.inputmethod.ExtractedText}.
+         */
+        public static final class ExtractedText
+                extends Values<android.view.inputmethod.ExtractedText> {
+            private ExtractedText(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+        }
+    }
+
+    /**
+     * @return an instance of {@link Completable.Int} that is associated with this
+     *         {@link CancellationGroup}.
+     */
+    @AnyThread
+    public Completable.Int createCompletableInt() {
+        return new Completable.Int(this);
+    }
+
+    /**
+     * @return an instance of {@link Completable.CharSequence} that is associated with this
+     *         {@link CancellationGroup}.
+     */
+    @AnyThread
+    public Completable.CharSequence createCompletableCharSequence() {
+        return new Completable.CharSequence(this);
+    }
+
+    /**
+     * @return an instance of {@link Completable.ExtractedText} that is associated with this
+     *         {@link CancellationGroup}.
+     */
+    @AnyThread
+    public Completable.ExtractedText createCompletableExtractedText() {
+        return new Completable.ExtractedText(this);
+    }
+
+    @AnyThread
+    private boolean registerLatch(@NonNull CountDownLatch latch) {
+        synchronized (mLock) {
+            if (mCanceled) {
+                return false;
+            }
+            if (mLatchList == null) {
+                // Set the initial capacity to 1 with an assumption that usually there is up to 1
+                // on-going operation.
+                mLatchList = new ArrayList<>(1);
+            }
+            mLatchList.add(latch);
+            return true;
+        }
+    }
+
+    @AnyThread
+    private void unregisterLatch(@NonNull CountDownLatch latch) {
+        synchronized (mLock) {
+            if (mLatchList != null) {
+                mLatchList.remove(latch);
+            }
+        }
+    }
+
+    /**
+     * Cancel all the completable objects created from this {@link CancellationGroup}.
+     *
+     * <p>Secondary calls will be silently ignored.</p>
+     */
+    @AnyThread
+    public void cancelAll() {
+        synchronized (mLock) {
+            if (!mCanceled) {
+                mCanceled = true;
+                if (mLatchList != null) {
+                    mLatchList.forEach(CountDownLatch::countDown);
+                    mLatchList.clear();
+                    mLatchList = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise.
+     */
+    @AnyThread
+    public boolean isCanceled() {
+        synchronized (mLock) {
+            return mCanceled;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
new file mode 100644
index 0000000..da56fd0
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.internal.inputmethod;
+
+oneway interface ICharSequenceResultCallback {
+    void onResult(in CharSequence result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
new file mode 100644
index 0000000..b603f6a
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.view.inputmethod.ExtractedText;
+
+oneway interface IExtractedTextResultCallback {
+    void onResult(in ExtractedText result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
new file mode 100644
index 0000000..bc5ed0d
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.internal.inputmethod;
+
+oneway interface IIntResultCallback {
+    void onResult(int result);
+}
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
new file mode 100644
index 0000000..44a8a83
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -0,0 +1,132 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Defines a set of factory methods to create {@link android.os.IBinder}-based callbacks that are
+ * associated with completable objects defined in {@link CancellationGroup.Completable}.
+ */
+public final class ResultCallbacks {
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private ResultCallbacks() {
+    }
+
+    @AnyThread
+    @Nullable
+    private static <T> T unwrap(@NonNull AtomicReference<WeakReference<T>> atomicRef) {
+        final WeakReference<T> ref = atomicRef.getAndSet(null);
+        if (ref == null) {
+            // Double-call is guaranteed to be ignored here.
+            return null;
+        }
+        final T value = ref.get();
+        ref.clear();
+        return value;
+    }
+
+    /**
+     * Creates {@link IIntResultCallback.Stub} that is to set
+     * {@link CancellationGroup.Completable.Int} when receiving the result.
+     *
+     * @param value {@link CancellationGroup.Completable.Int} to be set when receiving the result.
+     * @return {@link IIntResultCallback.Stub} that can be passed as a binder IPC parameter.
+     */
+    @AnyThread
+    public static IIntResultCallback.Stub of(@NonNull CancellationGroup.Completable.Int value) {
+        final AtomicReference<WeakReference<CancellationGroup.Completable.Int>>
+                atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+        return new IIntResultCallback.Stub() {
+            @BinderThread
+            @Override
+            public void onResult(int result) {
+                final CancellationGroup.Completable.Int value = unwrap(atomicRef);
+                if (value == null) {
+                    return;
+                }
+                value.onComplete(result);
+            }
+        };
+    }
+
+    /**
+     * Creates {@link ICharSequenceResultCallback.Stub} that is to set
+     * {@link CancellationGroup.Completable.CharSequence} when receiving the result.
+     *
+     * @param value {@link CancellationGroup.Completable.CharSequence} to be set when receiving the
+     *              result.
+     * @return {@link ICharSequenceResultCallback.Stub} that can be passed as a binder IPC
+     *         parameter.
+     */
+    @AnyThread
+    public static ICharSequenceResultCallback.Stub of(
+            @NonNull CancellationGroup.Completable.CharSequence value) {
+        final AtomicReference<WeakReference<CancellationGroup.Completable.CharSequence>> atomicRef =
+                new AtomicReference<>(new WeakReference<>(value));
+
+        return new ICharSequenceResultCallback.Stub() {
+            @BinderThread
+            @Override
+            public void onResult(CharSequence result) {
+                final CancellationGroup.Completable.CharSequence value = unwrap(atomicRef);
+                if (value == null) {
+                    return;
+                }
+                value.onComplete(result);
+            }
+        };
+    }
+
+    /**
+     * Creates {@link IExtractedTextResultCallback.Stub} that is to set
+     * {@link CancellationGroup.Completable.ExtractedText} when receiving the result.
+     *
+     * @param value {@link CancellationGroup.Completable.ExtractedText} to be set when receiving the
+     *              result.
+     * @return {@link IExtractedTextResultCallback.Stub} that can be passed as a binder IPC
+     *         parameter.
+     */
+    @AnyThread
+    public static IExtractedTextResultCallback.Stub of(
+            @NonNull CancellationGroup.Completable.ExtractedText value) {
+        final AtomicReference<WeakReference<CancellationGroup.Completable.ExtractedText>>
+                atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+        return new IExtractedTextResultCallback.Stub() {
+            @BinderThread
+            @Override
+            public void onResult(android.view.inputmethod.ExtractedText result) {
+                final CancellationGroup.Completable.ExtractedText value = unwrap(atomicRef);
+                if (value == null) {
+                    return;
+                }
+                value.onComplete(result);
+            }
+        };
+    }
+}
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 91ba0df..180ab08 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -85,7 +85,7 @@
     }
 
     @Override
-    public void logWithInstanceId(UiEventLogger.UiEventEnum event, int uid, String packageName,
+    public void logWithInstanceId(UiEventEnum event, int uid, String packageName,
             InstanceId instance) {
         final int eventId = event.getId();
         if (eventId > 0) {
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/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index 2779be6..575a532 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -69,6 +69,7 @@
     private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
     private final Rect mInsets = new Rect();
     private final int mSnapMode;
+    private final boolean mFreeSnapMode;
     private final int mMinimalSizeResizableTask;
     private final int mTaskHeightInMinimizedMode;
     private final float mFixedRatio;
@@ -125,6 +126,8 @@
         mInsets.set(insets);
         mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
                 res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+        mFreeSnapMode = res.getBoolean(
+                com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode);
         mFixedRatio = res.getFraction(
                 com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
         mMinimalSizeResizableTask = res.getDimensionPixelSize(
@@ -247,7 +250,20 @@
         }
     }
 
+    private boolean shouldApplyFreeSnapMode(int position) {
+        if (!mFreeSnapMode) {
+            return false;
+        }
+        if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) {
+            return false;
+        }
+        return mFirstSplitTarget.position < position && position < mLastSplitTarget.position;
+    }
+
     private SnapTarget snap(int position, boolean hardDismiss) {
+        if (shouldApplyFreeSnapMode(position)) {
+            return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+        }
         int minIndex = -1;
         float minDistance = Float.MAX_VALUE;
         int size = mTargets.size();
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 6278d4a3..9257c6d 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -29,6 +29,7 @@
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionInspector;
@@ -36,6 +37,9 @@
 import android.view.inputmethod.InputContentInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
 import com.android.internal.os.SomeArgs;
 
 public abstract class IInputConnectionWrapper extends IInputContext.Stub {
@@ -111,28 +115,31 @@
 
     abstract protected boolean isActive();
 
-    public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
-    }
-    
-    public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
+    public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback));
     }
 
-    public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
+    public void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_GET_TEXT_BEFORE_CURSOR, length, flags, callback));
     }
 
-    public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
+    public void getSelectedText(int flags, ICharSequenceResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback));
     }
 
-    public void getExtractedText(ExtractedTextRequest request,
-            int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
-                request, seq, callback));
+    public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
+        dispatchMessage(
+                mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
     }
-    
+
+    public void getExtractedText(ExtractedTextRequest request, int flags,
+            IExtractedTextResultCallback callback) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = request;
+        args.arg2 = callback;
+        dispatchMessage(mH.obtainMessage(DO_GET_EXTRACTED_TEXT, flags, 0 /* unused */, args));
+    }
+
     public void commitText(CharSequence text, int newCursorPosition) {
         dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
     }
@@ -199,10 +206,9 @@
         dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
     }
 
-    public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
-            IInputContextCallback callback) {
-        dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
-                seq, callback));
+    public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
+                0 /* unused */, callback));
     }
 
     public void closeConnection() {
@@ -210,9 +216,12 @@
     }
 
     public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts,
-            int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIOOSC(DO_COMMIT_CONTENT, flags, inputContentInfo, opts, seq,
-                callback));
+            IIntResultCallback callback) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = inputContentInfo;
+        args.arg2 = opts;
+        args.arg3 = callback;
+        dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args));
     }
 
     void dispatchMessage(Message msg) {
@@ -231,100 +240,97 @@
     void executeMessage(Message msg) {
         switch (msg.what) {
             case DO_GET_TEXT_AFTER_CURSOR: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final CharSequence result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
+                    result = null;
+                } else {
+                    result = ic.getTextAfterCursor(msg.arg1, msg.arg2);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
-                        callback.setTextAfterCursor(null, callbackSeq);
-                        return;
-                    }
-                    callback.setTextAfterCursor(ic.getTextAfterCursor(
-                            msg.arg1, msg.arg2), callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getTextAfterCursor()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_TEXT_BEFORE_CURSOR: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final CharSequence result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
+                    result = null;
+                } else {
+                    result = ic.getTextBeforeCursor(msg.arg1, msg.arg2);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
-                        callback.setTextBeforeCursor(null, callbackSeq);
-                        return;
-                    }
-                    callback.setTextBeforeCursor(ic.getTextBeforeCursor(
-                            msg.arg1, msg.arg2), callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getTextBeforeCursor()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_SELECTED_TEXT: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final CharSequence result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getSelectedText on inactive InputConnection");
+                    result = null;
+                } else {
+                    result = ic.getSelectedText(msg.arg1);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getSelectedText on inactive InputConnection");
-                        callback.setSelectedText(null, callbackSeq);
-                        return;
-                    }
-                    callback.setSelectedText(ic.getSelectedText(
-                            msg.arg1), callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setSelectedText", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getSelectedText()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_CURSOR_CAPS_MODE: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final int result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
+                    result = 0;
+                } else {
+                    result = ic.getCursorCapsMode(msg.arg1);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
-                        callback.setCursorCapsMode(0, callbackSeq);
-                        return;
-                    }
-                    callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
-                            callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getCursorCapsMode()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_EXTRACTED_TEXT: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final SomeArgs args = (SomeArgs) msg.obj;
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
+                    final ExtractedTextRequest request = (ExtractedTextRequest) args.arg1;
+                    final IExtractedTextResultCallback callback =
+                            (IExtractedTextResultCallback) args.arg2;
+                    final InputConnection ic = getInputConnection();
+                    final ExtractedText result;
                     if (ic == null || !isActive()) {
                         Log.w(TAG, "getExtractedText on inactive InputConnection");
-                        callback.setExtractedText(null, callbackSeq);
-                        return;
+                        result = null;
+                    } else {
+                        result = ic.getExtractedText(request, msg.arg1);
                     }
-                    callback.setExtractedText(ic.getExtractedText(
-                            (ExtractedTextRequest)args.arg1, msg.arg1), callbackSeq);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setExtractedText", e);
+                    try {
+                        callback.onResult(result);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to return the result to getExtractedText()."
+                                + " result=" + result, e);
+                    }
                 } finally {
                     args.recycle();
                 }
@@ -494,22 +500,20 @@
                 return;
             }
             case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final boolean result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+                    result = false;
+                } else {
+                    result = ic.requestCursorUpdates(msg.arg1);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
-                        callback.setRequestUpdateCursorAnchorInfoResult(false, callbackSeq);
-                        return;
-                    }
-                    callback.setRequestUpdateCursorAnchorInfoResult(
-                            ic.requestCursorUpdates(msg.arg1), callbackSeq);
+                    callback.onResult(result ? 1 : 0);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to requestCursorUpdates()."
+                            + " result=" + result, e);
                 }
                 return;
             }
@@ -547,26 +551,28 @@
                 final int flags = msg.arg1;
                 SomeArgs args = (SomeArgs) msg.obj;
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
+                    final IIntResultCallback callback = (IIntResultCallback) args.arg3;
+                    final InputConnection ic = getInputConnection();
+                    final boolean result;
                     if (ic == null || !isActive()) {
                         Log.w(TAG, "commitContent on inactive InputConnection");
-                        callback.setCommitContentResult(false, callbackSeq);
-                        return;
+                        result = false;
+                    } else {
+                        final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
+                        if (inputContentInfo == null || !inputContentInfo.validate()) {
+                            Log.w(TAG, "commitContent with invalid inputContentInfo="
+                                    + inputContentInfo);
+                            result = false;
+                        } else {
+                            result = ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
+                        }
                     }
-                    final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
-                    if (inputContentInfo == null || !inputContentInfo.validate()) {
-                        Log.w(TAG, "commitContent with invalid inputContentInfo="
-                                + inputContentInfo);
-                        callback.setCommitContentResult(false, callbackSeq);
-                        return;
+                    try {
+                        callback.onResult(result ? 1 : 0);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to return the result to commitContent()."
+                                + " result=" + result, e);
                     }
-                    final boolean result =
-                            ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
-                    callback.setCommitContentResult(result, callbackSeq);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling commitContent", e);
                 } finally {
                     args.recycle();
                 }
@@ -588,40 +594,6 @@
         return mH.obtainMessage(what, 0, 0, arg1);
     }
 
-    Message obtainMessageISC(int what, int arg1, int callbackSeq, IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, 0, args);
-    }
-
-    Message obtainMessageIISC(int what, int arg1, int arg2, int callbackSeq,
-            IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, arg2, args);
-    }
-
-    Message obtainMessageIOOSC(int what, int arg1, Object objArg1, Object objArg2, int callbackSeq,
-            IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg1 = objArg1;
-        args.arg2 = objArg2;
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, 0, args);
-    }
-
-    Message obtainMessageIOSC(int what, int arg1, Object arg2, int callbackSeq,
-            IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg1 = arg2;
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, 0, args);
-    }
-
     Message obtainMessageIO(int what, int arg1, Object arg2) {
         return mH.obtainMessage(what, arg1, 0, arg2);
     }
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index c227991..86f1293 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -23,7 +23,9 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputContentInfo;
 
-import com.android.internal.view.IInputContextCallback;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
 
 /**
  * Interface from an input method to the application, allowing it to perform
@@ -31,14 +33,14 @@
  * {@hide}
  */
  oneway interface IInputContext {
-    void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback); 
+    void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback);
 
-    void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback);
-    
-    void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
-    
-    void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
-            IInputContextCallback callback);
+    void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback);
+
+    void getCursorCapsMode(int reqModes, IIntResultCallback callback);
+
+    void getExtractedText(in ExtractedTextRequest request, int flags,
+            IExtractedTextResultCallback callback);
 
     void deleteSurroundingText(int beforeLength, int afterLength);
     void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
@@ -71,11 +73,10 @@
 
     void setComposingRegion(int start, int end);
 
-    void getSelectedText(int flags, int seq, IInputContextCallback callback);
+    void getSelectedText(int flags, ICharSequenceResultCallback callback);
 
-    void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
-            IInputContextCallback callback);
+    void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback);
 
-    void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec,
-            IInputContextCallback callback);
+    void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts,
+            IIntResultCallback callback);
 }
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
deleted file mode 100644
index 0f40a83..0000000
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2008 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.internal.view;
-
-import android.view.inputmethod.ExtractedText;
-
-/**
- * {@hide}
- */
-oneway interface IInputContextCallback {
-    void setTextBeforeCursor(CharSequence textBeforeCursor, int seq);
-    void setTextAfterCursor(CharSequence textAfterCursor, int seq);
-    void setCursorCapsMode(int capsMode, int seq);
-    void setExtractedText(in ExtractedText extractedText, int seq);
-    void setSelectedText(CharSequence selectedText, int seq);
-    void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq);
-    void setCommitContentResult(boolean result, int seq);
-}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index a41048c..0bf5234 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -17,14 +17,12 @@
 package com.android.internal.view;
 
 import android.annotation.AnyThread;
-import android.annotation.BinderThread;
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
 import android.inputmethodservice.AbstractInputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -36,10 +34,15 @@
 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
 import android.view.inputmethod.InputContentInfo;
 
+import com.android.internal.inputmethod.CancellationGroup;
+import com.android.internal.inputmethod.ResultCallbacks;
+
 import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
 
 public class InputConnectionWrapper implements InputConnection {
+    private static final String TAG = "InputConnectionWrapper";
+
     private static final int MAX_WAIT_TIME_MILLIS = 2000;
     private final IInputContext mIInputContext;
     @NonNull
@@ -49,257 +52,94 @@
     private final int mMissingMethods;
 
     /**
-     * {@code true} if the system already decided to take away IME focus from the target app. This
-     * can be signaled even when the corresponding signal is in the task queue and
-     * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread.
+     * Signaled when the system decided to take away IME focus from the target app.
+     *
+     * <p>This is expected to be signaled immediately when the IME process receives
+     * {@link IInputMethod#unbindInput()}.</p>
      */
     @NonNull
-    private final AtomicBoolean mIsUnbindIssued;
-
-    static class InputContextCallback extends IInputContextCallback.Stub {
-        private static final String TAG = "InputConnectionWrapper.ICC";
-        public int mSeq;
-        public boolean mHaveValue;
-        public CharSequence mTextBeforeCursor;
-        public CharSequence mTextAfterCursor;
-        public CharSequence mSelectedText;
-        public ExtractedText mExtractedText;
-        public int mCursorCapsMode;
-        public boolean mRequestUpdateCursorAnchorInfoResult;
-        public boolean mCommitContentResult;
-
-        // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
-        // exclusive access to this object.
-        private static InputContextCallback sInstance = new InputContextCallback();
-        private static int sSequenceNumber = 1;
-        
-        /**
-         * Returns an InputContextCallback object that is guaranteed not to be in use by
-         * any other thread.  The returned object's 'have value' flag is cleared and its expected
-         * sequence number is set to a new integer.  We use a sequence number so that replies that
-         * occur after a timeout has expired are not interpreted as replies to a later request.
-         */
-        @UnsupportedAppUsage
-        @AnyThread
-        private static InputContextCallback getInstance() {
-            synchronized (InputContextCallback.class) {
-                // Return sInstance if it's non-null, otherwise construct a new callback
-                InputContextCallback callback;
-                if (sInstance != null) {
-                    callback = sInstance;
-                    sInstance = null;
-                    
-                    // Reset the callback
-                    callback.mHaveValue = false;
-                } else {
-                    callback = new InputContextCallback();
-                }
-                
-                // Set the sequence number
-                callback.mSeq = sSequenceNumber++;
-                return callback;
-            }
-        }
-        
-        /**
-         * Makes the given InputContextCallback available for use in the future.
-         */
-        @UnsupportedAppUsage
-        @AnyThread
-        private void dispose() {
-            synchronized (InputContextCallback.class) {
-                // If sInstance is non-null, just let this object be garbage-collected
-                if (sInstance == null) {
-                    // Allow any objects being held to be gc'ed
-                    mTextAfterCursor = null;
-                    mTextBeforeCursor = null;
-                    mExtractedText = null;
-                    sInstance = this;
-                }
-            }
-        }
-
-        @BinderThread
-        public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mTextBeforeCursor = textBeforeCursor;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setTextBeforeCursor, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mTextAfterCursor = textAfterCursor;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setTextAfterCursor, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setSelectedText(CharSequence selectedText, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mSelectedText = selectedText;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setSelectedText, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setCursorCapsMode(int capsMode, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mCursorCapsMode = capsMode; 
-                    mHaveValue = true;  
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setCursorCapsMode, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setExtractedText(ExtractedText extractedText, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mExtractedText = extractedText;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setExtractedText, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mRequestUpdateCursorAnchorInfoResult = result;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setCursorAnchorInfoRequestResult, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setCommitContentResult(boolean result, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mCommitContentResult = result;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setCommitContentResult, ignoring.");
-                }
-            }
-        }
-
-        /**
-         * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
-         * 
-         * <p>The caller must be synchronized on this callback object.
-         */
-        @AnyThread
-        void waitForResultLocked() {
-            long startTime = SystemClock.uptimeMillis();
-            long endTime = startTime + MAX_WAIT_TIME_MILLIS;
-
-            while (!mHaveValue) {
-                long remainingTime = endTime - SystemClock.uptimeMillis();
-                if (remainingTime <= 0) {
-                    Log.w(TAG, "Timed out waiting on IInputContextCallback");
-                    return;
-                }
-                try {
-                    wait(remainingTime);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-    }
+    private final CancellationGroup mCancellationGroup;
 
     public InputConnectionWrapper(
             @NonNull WeakReference<AbstractInputMethodService> inputMethodService,
-            IInputContext inputContext, @MissingMethodFlags final int missingMethods,
-            @NonNull AtomicBoolean isUnbindIssued) {
+            IInputContext inputContext, @MissingMethodFlags int missingMethods,
+            @NonNull CancellationGroup cancellationGroup) {
         mInputMethodService = inputMethodService;
         mIInputContext = inputContext;
         mMissingMethods = missingMethods;
-        mIsUnbindIssued = isUnbindIssued;
+        mCancellationGroup = cancellationGroup;
+    }
+
+    @AnyThread
+    private static void logInternal(@Nullable String methodName, boolean timedOut,
+            @Nullable Object defaultValue) {
+        if (timedOut) {
+            Log.w(TAG, methodName + " didn't respond in " + MAX_WAIT_TIME_MILLIS + " msec."
+                    + " Returning default: " + defaultValue);
+        } else {
+            Log.w(TAG, methodName + " was canceled before complete. Returning default: "
+                    + defaultValue);
+        }
+    }
+
+    @AnyThread
+    private static int getResultOrZero(@NonNull CancellationGroup.Completable.Int value,
+             @NonNull String methodName) {
+        final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS,  TimeUnit.MILLISECONDS);
+        if (value.hasValue()) {
+            return value.getValue();
+        }
+        logInternal(methodName, timedOut, 0);
+        return 0;
+    }
+
+    @AnyThread
+    @Nullable
+    private static <T> T getResultOrNull(@NonNull CancellationGroup.Completable.Values<T> value,
+            @NonNull String methodName) {
+        final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS,  TimeUnit.MILLISECONDS);
+        if (value.hasValue()) {
+            return value.getValue();
+        }
+        logInternal(methodName, timedOut, null);
+        return null;
     }
 
     @AnyThread
     public CharSequence getTextAfterCursor(int length, int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
-        CharSequence value = null;
+        final CancellationGroup.Completable.CharSequence value =
+                mCancellationGroup.createCompletableCharSequence();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mTextAfterCursor;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getTextAfterCursor()");
     }
 
     @AnyThread
     public CharSequence getTextBeforeCursor(int length, int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
-        CharSequence value = null;
+        final CancellationGroup.Completable.CharSequence value =
+                mCancellationGroup.createCompletableCharSequence();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mTextBeforeCursor;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getTextBeforeCursor()");
     }
 
     @AnyThread
     public CharSequence getSelectedText(int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
@@ -307,67 +147,46 @@
             // This method is not implemented.
             return null;
         }
-        CharSequence value = null;
+        final CancellationGroup.Completable.CharSequence value =
+                mCancellationGroup.createCompletableCharSequence();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getSelectedText(flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mSelectedText;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getSelectedText(flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getSelectedText()");
     }
 
     @AnyThread
     public int getCursorCapsMode(int reqModes) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return 0;
         }
 
-        int value = 0;
+        final CancellationGroup.Completable.Int value =
+                mCancellationGroup.createCompletableInt();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mCursorCapsMode;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return 0;
         }
-        return value;
+        return getResultOrZero(value, "getCursorCapsMode()");
     }
 
     @AnyThread
     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
-        ExtractedText value = null;
+        final CancellationGroup.Completable.ExtractedText value =
+                mCancellationGroup.createCompletableExtractedText();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mExtractedText;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getExtractedText()");
     }
 
     @AnyThread
@@ -563,29 +382,22 @@
 
     @AnyThread
     public boolean requestCursorUpdates(int cursorUpdateMode) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return false;
         }
 
-        boolean result = false;
         if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
             // This method is not implemented.
             return false;
         }
+        final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    result = callback.mRequestUpdateCursorAnchorInfoResult;
-                }
-            }
-            callback.dispose();
+            mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode,
+                    ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return false;
         }
-        return result;
+        return getResultOrZero(value, "requestUpdateCursorAnchorInfo()") != 0;
     }
 
     @AnyThread
@@ -601,38 +413,31 @@
 
     @AnyThread
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return false;
         }
 
-        boolean result = false;
         if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
             // This method is not implemented.
             return false;
         }
-        try {
-            if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
-                final AbstractInputMethodService inputMethodService = mInputMethodService.get();
-                if (inputMethodService == null) {
-                    // This basically should not happen, because it's the the caller of this method.
-                    return false;
-                }
-                inputMethodService.exposeContent(inputContentInfo, this);
-            }
 
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    result = callback.mCommitContentResult;
-                }
+        if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+            final AbstractInputMethodService inputMethodService = mInputMethodService.get();
+            if (inputMethodService == null) {
+                // This basically should not happen, because it's the caller of this method.
+                return false;
             }
-            callback.dispose();
+            inputMethodService.exposeContent(inputContentInfo, this);
+        }
+
+        final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
+        try {
+            mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return false;
         }
-        return result;
+        return getResultOrZero(value, "commitContent()") != 0;
     }
 
     @AnyThread
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index ab68c44..523c749 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -331,7 +331,8 @@
     @RemotableViewMethod
     public void setIsImportantConversation(boolean isImportantConversation) {
         mImportantConversation = isImportantConversation;
-        mImportanceRingView.setVisibility(isImportantConversation ? VISIBLE : GONE);
+        mImportanceRingView.setVisibility(isImportantConversation
+                && mIcon.getVisibility() != GONE ? VISIBLE : GONE);
     }
 
     public boolean isImportantConversation() {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b1bba53..340dd4d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3346,6 +3346,10 @@
          This should only be set when the device has gestural navigation enabled by default. -->
     <bool name="config_showGesturalNavigationHints">false</bool>
 
+    <!-- Controls the free snap mode for the docked stack divider. In this mode, the divider can be
+         snapped to any position between the first target and the last target. -->
+    <bool name="config_dockedStackDividerFreeSnapMode">false</bool>
+
     <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
          These values are in DPs and will be converted to pixel sizes internally. -->
     <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ec80582..11dda41 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1667,6 +1667,7 @@
   <java-symbol type="bool" name="config_perDisplayFocusEnabled" />
   <java-symbol type="bool" name="config_showNavigationBar" />
   <java-symbol type="bool" name="config_supportAutoRotation" />
+  <java-symbol type="bool" name="config_dockedStackDividerFreeSnapMode" />
   <java-symbol type="dimen" name="docked_stack_divider_thickness" />
   <java-symbol type="dimen" name="docked_stack_divider_insets" />
   <java-symbol type="dimen" name="docked_stack_minimize_thickness" />
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
deleted file mode 100644
index 2b14bca..0000000
--- a/core/tests/ResourceLoaderTests/Android.bp
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// 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: "FrameworksResourceLoaderTests",
-    srcs: [
-        "src/**/*.kt"
-    ],
-    libs: [
-        "android.test.runner",
-        "android.test.base"
-    ],
-    static_libs: [
-        "FrameworksResourceLoaderTests_Providers",
-        "androidx.test.espresso.core",
-        "androidx.test.ext.junit",
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "mockito-target-minus-junit4",
-        "truth-prebuilt"
-    ],
-    resource_dirs: ["res", "resources/provider_stable/res"],
-    platform_apis: true,
-    test_suites: ["device-tests"],
-    aaptflags: ["-0 .txt"],
-    data: [
-        ":FrameworksResourceLoaderTests_ProviderOne_Split",
-        ":FrameworksResourceLoaderTests_ProviderTwo_Split",
-        ":FrameworksResourceLoaderTests_ProviderThree_Split",
-        ":FrameworksResourceLoaderTests_ProviderFour_Split"
-    ]
-}
-
-java_genrule {
-    name: "FrameworksResourceLoaderTests_Providers",
-    tools: ["soong_zip"],
-    srcs : [
-        ":FrameworksResourceLoaderTests_ProviderOne",
-        ":FrameworksResourceLoaderTests_ProviderOne_ARSC",
-        ":FrameworksResourceLoaderTests_ProviderTwo",
-        ":FrameworksResourceLoaderTests_ProviderTwo_ARSC",
-        ":FrameworksResourceLoaderTests_ProviderThree",
-        ":FrameworksResourceLoaderTests_ProviderThree_ARSC",
-        ":FrameworksResourceLoaderTests_ProviderFour",
-        ":FrameworksResourceLoaderTests_ProviderFour_ARSC"
-    ],
-    out: ["FrameworksResourceLoaderTests_Providers.jar"],
-    cmd: "mkdir -p $(genDir)/assets/ && cp $(in) $(genDir)/assets/ && " +
-         "$(location soong_zip) -o $(out) " +
-         "-L 0 -C $(genDir) -D $(genDir)/assets/"
-}
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/AndroidManifest.xml b/core/tests/ResourceLoaderTests/AndroidManifest.xml
deleted file mode 100644
index 00b4ccb..0000000
--- a/core/tests/ResourceLoaderTests/AndroidManifest.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?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
-  -->
-
-<!-- Split loading is tested separately, so this must be marked isolated -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.res.loader.test"
-    android:isolatedSplits="true"
-    >
-
-    <uses-sdk android:minSdkVersion="29"/>
-
-    <application>
-        <uses-library android:name="android.test.runner"/>
-
-        <activity
-            android:name=".TestActivity"
-            android:configChanges="orientation"
-            />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="ResourceLoaderTests"
-        android:targetPackage="android.content.res.loader.test"
-        />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml
deleted file mode 100644
index 800e7a7..0000000
--- a/core/tests/ResourceLoaderTests/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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.
-  -->
-
-<configuration description="Test module config for ResourceLoaderTests">
-    <option name="test-tag" value="ResourceLoaderTests" />
-
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
-        <option name="cleanup-apks" value="true" />
-        <!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
-        <option name="split-apk-file-names"
-            value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTests_ProviderOne_Split.apk,FrameworksResourceLoaderTests_ProviderTwo_Split.apk,FrameworksResourceLoaderTests_ProviderThree_Split.apk,FrameworksResourceLoaderTests_ProviderFour_Split.apk" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="android.content.res.loader.test" />
-    </test>
-</configuration>
diff --git a/core/tests/ResourceLoaderTests/assets/asset.txt b/core/tests/ResourceLoaderTests/assets/asset.txt
deleted file mode 100644
index 271704b..0000000
--- a/core/tests/ResourceLoaderTests/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-In assets directory
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/assets/base_asset.txt b/core/tests/ResourceLoaderTests/assets/base_asset.txt
deleted file mode 100644
index 8e62cc3..0000000
--- a/core/tests/ResourceLoaderTests/assets/base_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Base
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
deleted file mode 100644
index a12e33a..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
deleted file mode 100644
index 182cbab..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
deleted file mode 100644
index e6b5f15b..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
deleted file mode 100644
index e9c743c..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
deleted file mode 100644
index cd05360..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
deleted file mode 100644
index dc8aa90..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
deleted file mode 100644
index 8a672ba..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
deleted file mode 100644
index 56f3d1e..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
deleted file mode 100644
index 663d312..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
deleted file mode 100644
index 5f6e4b8..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 8102d15..0000000
--- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index d1211c5..0000000
--- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
-  -->
-
-<color
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#B2D2F2"
-    />
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
deleted file mode 100644
index 05499ed..0000000
--- a/core/tests/ResourceLoaderTests/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-  -->
-
-<MysteryLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    />
-
diff --git a/core/tests/ResourceLoaderTests/res/values/values.xml b/core/tests/ResourceLoaderTests/res/values/values.xml
deleted file mode 100644
index ad78532..0000000
--- a/core/tests/ResourceLoaderTests/res/values/values.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <dimen name="test">0dp</dimen>
-    <string name="test">Not overlaid</string>
-</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/Android.bp b/core/tests/ResourceLoaderTests/resources/Android.bp
deleted file mode 100644
index 18ef64b..0000000
--- a/core/tests/ResourceLoaderTests/resources/Android.bp
+++ /dev/null
@@ -1,115 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderOne",
-    manifest: "AndroidManifestApp.xml",
-    asset_dirs: ["provider1/assets"],
-    resource_dirs: ["provider1/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderTwo",
-    manifest: "AndroidManifestApp.xml",
-    asset_dirs: ["provider2/assets"],
-    resource_dirs: ["provider2/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderThree",
-    manifest: "AndroidManifestApp.xml",
-    asset_dirs: ["provider3/assets"],
-    resource_dirs: ["provider3/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderFour",
-    manifest: "AndroidManifestApp.xml",
-    asset_dirs: ["provider4/assets"],
-    resource_dirs: ["provider4/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
-
-// Resources.arsc(s)
-
-genrule {
-    name: "FrameworksResourceLoaderTests_ProviderOne_ARSC",
-    srcs: [":FrameworksResourceLoaderTests_ProviderOne"],
-    cmd: "unzip $(in) resources.arsc -d $(genDir) && "
-         + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderOne.arsc",
-    out: ["FrameworksResourceLoaderTests_ProviderOne.arsc"]
-}
-
-genrule {
-    name: "FrameworksResourceLoaderTests_ProviderTwo_ARSC",
-    srcs: [":FrameworksResourceLoaderTests_ProviderTwo"],
-    cmd: "unzip $(in) resources.arsc -d $(genDir) && "
-         + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderTwo.arsc",
-    out: ["FrameworksResourceLoaderTests_ProviderTwo.arsc"]
-}
-
-genrule {
-    name: "FrameworksResourceLoaderTests_ProviderThree_ARSC",
-    srcs: [":FrameworksResourceLoaderTests_ProviderThree"],
-    cmd: "unzip $(in) resources.arsc -d $(genDir) && "
-         + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderThree.arsc",
-    out: ["FrameworksResourceLoaderTests_ProviderThree.arsc"]
-}
-
-genrule {
-    name: "FrameworksResourceLoaderTests_ProviderFour_ARSC",
-    srcs: [":FrameworksResourceLoaderTests_ProviderFour"],
-    cmd: "unzip $(in) resources.arsc -d $(genDir) && "
-         + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderFour.arsc",
-    out: ["FrameworksResourceLoaderTests_ProviderFour.arsc"]
-}
-
-// Split APKs
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderOne_Split",
-    manifest: "AndroidManifestSplit1.xml",
-    asset_dirs: ["provider1/assets"],
-    resource_dirs: ["provider1/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderTwo_Split",
-    manifest: "AndroidManifestSplit2.xml",
-    asset_dirs: ["provider2/assets"],
-    resource_dirs: ["provider2/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderThree_Split",
-    manifest: "AndroidManifestSplit3.xml",
-    asset_dirs: ["provider3/assets"],
-    resource_dirs: ["provider3/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
-    name: "FrameworksResourceLoaderTests_ProviderFour_Split",
-    manifest: "AndroidManifestSplit4.xml",
-    asset_dirs: ["provider4/assets"],
-    resource_dirs: ["provider4/res", "provider_stable/res"],
-    aaptflags: ["-0 .txt"]
-}
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
deleted file mode 100644
index c8a3590..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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="android.content.res.loader.test">
-
-    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
-    <application/>
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
deleted file mode 100644
index d5fa83f..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
-  -->
-
-<!-- Mocks the framework package name so that AAPT2 assigns the correct package -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android">
-
-    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
-    <application/>
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml
deleted file mode 100644
index 5cd4227..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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"
-    package="android.content.res.loader.test"
-    split="FrameworksResourceLoaderTests_ProviderOne_Split"
-    android:isFeatureSplit="true"
-    >
-
-    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
-    <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml
deleted file mode 100644
index b5180e6..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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="android.content.res.loader.test"
-    split="FrameworksResourceLoaderTests_ProviderTwo_Split"
-    android:isFeatureSplit="true"
-    >
-
-    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
-    <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml
deleted file mode 100644
index 8ddb892..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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="android.content.res.loader.test"
-    split="FrameworksResourceLoaderTests_ProviderThree_Split"
-    android:isFeatureSplit="true"
-    >
-
-    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
-    <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml
deleted file mode 100644
index b6bf552..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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="android.content.res.loader.test"
-    split="FrameworksResourceLoaderTests_ProviderFour_Split"
-    android:isFeatureSplit="true"
-    >
-
-    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
-    <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png
deleted file mode 100644
index f3e53d7..0000000
--- a/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml
deleted file mode 100644
index d59059b..0000000
--- a/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-  -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    />
-
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml b/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml
deleted file mode 100644
index 2e50182..0000000
--- a/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <public type="drawable" name="ic_delete" id="0x0108001d" />
-    <public type="layout" name="activity_list_item" id="0x01090000" />
-    <public type="string" name="cancel" id="0x01040000" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml
deleted file mode 100644
index 5f6e90c..0000000
--- a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <string name="cancel">SomeRidiculouslyUnlikelyString</string>
-</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt
deleted file mode 100644
index 6dcd8e4..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-One
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt
deleted file mode 100644
index 0e41ffa..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderOne
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 4eb8ca3..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 57a8cf1..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
-  -->
-
-<color
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#000001"
-    />
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml
deleted file mode 100644
index ede3838..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-  -->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml
deleted file mode 100644
index 5ef75d5..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <dimen name="test">100dp</dimen>
-    <string name="test">One</string>
-
-    <string name="additional">One</string>
-    <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt
deleted file mode 100644
index 5673baa..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Two
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt
deleted file mode 100644
index bca782e..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderTwo
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 671d6d0..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 333fe34..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
-  -->
-
-<color
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#000002"
-    />
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml
deleted file mode 100644
index d8bff90..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml
deleted file mode 100644
index 387c519..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <dimen name="test">200dp</dimen>
-    <string name="test">Two</string>
-
-    <string name="additional">Two</string>
-    <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt
deleted file mode 100644
index 368c34d..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Three
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt
deleted file mode 100644
index bae8ef7..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderThree
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 5231d17..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 41095d4..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
-  -->
-
-<color
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#000003"
-    />
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml
deleted file mode 100644
index d58d3db..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-  -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml
deleted file mode 100644
index ab75bfa..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <dimen name="test">300dp</dimen>
-    <string name="test">Three</string>
-
-    <string name="additional">Three</string>
-    <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt
deleted file mode 100644
index ad70cdd..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Four
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt
deleted file mode 100644
index b75d996..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderFour
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index e9a4cfc..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 0623245..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
-  -->
-
-<color
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#000004"
-    />
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml
deleted file mode 100644
index ab9e265..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-  -->
-
-<TableLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml
deleted file mode 100644
index 896993e..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <dimen name="test">400dp</dimen>
-    <string name="test">Four</string>
-
-    <string name="additional">Four</string>
-    <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml
deleted file mode 100644
index 29918d7..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml b/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml
deleted file mode 100644
index 269c40f..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <public type="dimen" name="test" id="0x7f010000" />
-    <public type="drawable" name="drawable_png" id="0x7f020000" />
-    <public type="drawable" name="drawable_xml" id="0x7f020001" />
-    <public type="layout" name="layout" id="0x7f030000" />
-    <public type="string" name="test" id="0x7f040000" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
deleted file mode 100644
index ec6a605..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.content.res.loader.test
-
-import android.content.Context
-import android.content.res.AssetFileDescriptor
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.content.res.loader.AssetsProvider
-import android.content.res.loader.ResourcesProvider
-import android.os.ParcelFileDescriptor
-import android.system.Os
-import android.util.ArrayMap
-import androidx.test.InstrumentationRegistry
-import org.json.JSONObject
-import org.junit.After
-import org.junit.Before
-import java.io.Closeable
-import java.io.FileOutputStream
-import java.io.File
-import java.io.FileDescriptor
-import java.util.zip.ZipInputStream
-
-abstract class ResourceLoaderTestBase {
-    protected val PROVIDER_ONE: String = "FrameworksResourceLoaderTests_ProviderOne"
-    protected val PROVIDER_TWO: String = "FrameworksResourceLoaderTests_ProviderTwo"
-    protected val PROVIDER_THREE: String = "FrameworksResourceLoaderTests_ProviderThree"
-    protected val PROVIDER_FOUR: String = "FrameworksResourceLoaderTests_ProviderFour"
-    protected val PROVIDER_EMPTY: String = "empty"
-
-    companion object {
-        /** Converts the map to a stable JSON string representation. */
-        fun mapToString(m: Map<String, String>): String {
-            return JSONObject(ArrayMap<String, String>().apply { putAll(m) }).toString()
-        }
-
-        /** Creates a lambda that runs multiple resources queries and concatenates the results. */
-        fun query(queries: Map<String, (Resources) -> String>): Resources.() -> String {
-            return {
-                val resultMap = ArrayMap<String, String>()
-                queries.forEach { q ->
-                    resultMap[q.key] = try {
-                        q.value.invoke(this)
-                    } catch (e: Exception) {
-                        e.javaClass.simpleName
-                    }
-                }
-                mapToString(resultMap)
-            }
-        }
-    }
-
-    // Data type of the current test iteration
-    open lateinit var dataType: DataType
-
-    protected lateinit var context: Context
-    protected lateinit var resources: Resources
-
-    // Track opened streams and ResourcesProviders to close them after testing
-    private val openedObjects = mutableListOf<Closeable>()
-
-    @Before
-    fun setUpBase() {
-        context = InstrumentationRegistry.getTargetContext()
-                .createConfigurationContext(Configuration())
-        resources = context.resources
-    }
-
-    @After
-    fun removeAllLoaders() {
-        resources.clearLoaders()
-        context.applicationContext.resources.clearLoaders()
-        openedObjects.forEach {
-            try {
-                it.close()
-            } catch (ignored: Exception) {
-            }
-        }
-    }
-
-    protected fun String.openProvider(dataType: DataType,
-                                      assetsProvider: MemoryAssetsProvider?): ResourcesProvider {
-        if (assetsProvider != null) {
-            openedObjects += assetsProvider
-        }
-        return when (dataType) {
-            DataType.APK_DISK_FD -> {
-                val file = context.copiedAssetFile("$this.apk")
-                ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd),
-                        assetsProvider).apply {
-                    file.close()
-                }
-            }
-            DataType.APK_DISK_FD_OFFSETS -> {
-                val asset = context.assets.openFd("$this.apk")
-                ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset,
-                        asset.length, assetsProvider).apply {
-                    asset.close()
-                }
-            }
-            DataType.ARSC_DISK_FD -> {
-                val file = context.copiedAssetFile("$this.arsc")
-                ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd),
-                        assetsProvider).apply {
-                    file.close()
-                }
-            }
-            DataType.ARSC_DISK_FD_OFFSETS -> {
-                val asset = context.assets.openFd("$this.arsc")
-                ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset,
-                        asset.length, assetsProvider).apply {
-                    asset.close()
-                }
-            }
-            DataType.APK_RAM_OFFSETS -> {
-                val asset = context.assets.openFd("$this.apk")
-                val leadingGarbageSize = 100L
-                val trailingGarbageSize = 55L
-                val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
-                        trailingGarbageSize.toInt())
-                ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength,
-                        assetsProvider).apply {
-                    asset.close()
-                    fd.close()
-                }
-            }
-            DataType.APK_RAM_FD -> {
-                val asset = context.assets.openFd("$this.apk")
-                var fd = loadAssetIntoMemory(asset)
-                ResourcesProvider.loadFromApk(fd, assetsProvider).apply {
-                    asset.close()
-                    fd.close()
-                }
-            }
-            DataType.ARSC_RAM_MEMORY -> {
-                val asset = context.assets.openFd("$this.arsc")
-                var fd = loadAssetIntoMemory(asset)
-                ResourcesProvider.loadFromTable(fd, assetsProvider).apply {
-                    asset.close()
-                    fd.close()
-                }
-            }
-            DataType.ARSC_RAM_MEMORY_OFFSETS -> {
-                val asset = context.assets.openFd("$this.arsc")
-                val leadingGarbageSize = 100L
-                val trailingGarbageSize = 55L
-                val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
-                        trailingGarbageSize.toInt())
-                ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength,
-                        assetsProvider).apply {
-                    asset.close()
-                    fd.close()
-                }
-            }
-            DataType.EMPTY -> {
-                if (equals(PROVIDER_EMPTY)) {
-                    ResourcesProvider.empty(EmptyAssetsProvider())
-                } else {
-                    if (assetsProvider == null) ResourcesProvider.empty(ZipAssetsProvider(this))
-                        else ResourcesProvider.empty(assetsProvider)
-                }
-            }
-            DataType.DIRECTORY -> {
-                ResourcesProvider.loadFromDirectory(zipToDir("$this.apk").absolutePath,
-                        assetsProvider)
-            }
-            DataType.SPLIT -> {
-                ResourcesProvider.loadFromSplit(context, "${this}_Split")
-            }
-        }
-    }
-
-    class EmptyAssetsProvider : AssetsProvider
-
-    /** An AssetsProvider that reads from a zip asset. */
-    inner class ZipAssetsProvider(val providerName: String) : AssetsProvider {
-        val root: File = zipToDir("$providerName.apk")
-
-        override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
-            val f = File(root, path)
-            return if (f.exists()) AssetFileDescriptor(
-                    ParcelFileDescriptor.open(File(root, path),
-                            ParcelFileDescriptor.MODE_READ_ONLY), 0,
-                    AssetFileDescriptor.UNKNOWN_LENGTH) else null
-        }
-    }
-
-    /** AssetsProvider for testing that returns file descriptors to files in RAM. */
-    class MemoryAssetsProvider : AssetsProvider, Closeable {
-        var loadAssetResults = HashMap<String, FileDescriptor>()
-
-        fun addLoadAssetFdResult(path: String, value: String) = apply {
-            val fd = Os.memfd_create(path, 0)
-            val valueBytes = value.toByteArray()
-            Os.write(fd, valueBytes, 0, valueBytes.size)
-            loadAssetResults[path] = fd
-        }
-
-        override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
-            return if (loadAssetResults.containsKey(path)) AssetFileDescriptor(
-                    ParcelFileDescriptor.dup(loadAssetResults[path]), 0,
-                    AssetFileDescriptor.UNKNOWN_LENGTH) else null
-        }
-
-        override fun close() {
-            for (f in loadAssetResults.values) {
-                Os.close(f)
-            }
-        }
-    }
-
-    /** Extracts an archive-based asset into a directory on disk. */
-    private fun zipToDir(name: String): File {
-        val root = File(context.filesDir, name.split('.')[0])
-        if (root.exists()) {
-            return root
-        }
-
-        root.mkdir()
-        ZipInputStream(context.assets.open(name)).use { zis ->
-            while (true) {
-                val entry = zis.nextEntry ?: break
-                val file = File(root, entry.name)
-                if (entry.isDirectory) {
-                    continue
-                }
-
-                file.parentFile.mkdirs()
-                file.outputStream().use { output ->
-                    var b = zis.read()
-                    while (b != -1) {
-                        output.write(b)
-                        b = zis.read()
-                    }
-                }
-            }
-        }
-        return root
-    }
-
-    /** Loads the asset into a temporary file stored in RAM. */
-    private fun loadAssetIntoMemory(
-        asset: AssetFileDescriptor,
-        leadingGarbageSize: Int = 0,
-        trailingGarbageSize: Int = 0
-    ): ParcelFileDescriptor {
-        val originalFd = Os.memfd_create(asset.toString(), 0 /* flags */)
-        val fd = ParcelFileDescriptor.dup(originalFd)
-        Os.close(originalFd)
-
-        val input = asset.createInputStream()
-        FileOutputStream(fd.fileDescriptor).use { output ->
-            // Add garbage before the APK data
-            for (i in 0 until leadingGarbageSize) {
-                output.write(Math.random().toInt())
-            }
-
-            for (i in 0 until asset.length.toInt()) {
-                output.write(input.read())
-            }
-
-            // Add garbage after the APK data
-            for (i in 0 until trailingGarbageSize) {
-                output.write(Math.random().toInt())
-            }
-        }
-
-        return fd
-    }
-
-    enum class DataType {
-        APK_DISK_FD,
-        APK_DISK_FD_OFFSETS,
-        APK_RAM_FD,
-        APK_RAM_OFFSETS,
-        ARSC_DISK_FD,
-        ARSC_DISK_FD_OFFSETS,
-        ARSC_RAM_MEMORY,
-        ARSC_RAM_MEMORY_OFFSETS,
-        EMPTY,
-        DIRECTORY,
-        SPLIT
-    }
-}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
deleted file mode 100644
index 5aa8814..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ /dev/null
@@ -1,815 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.content.res.loader.test
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.content.res.AssetManager
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.content.res.loader.ResourcesLoader
-import android.graphics.Color
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.ColorDrawable
-import android.os.IBinder
-import androidx.test.rule.ActivityTestRule
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotEquals
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import java.util.Collections
-
-/**
- * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
- * the behavior being verified isn't specific to any resource type. As long as it can pass an
- * equals check.
- *
- * Currently tests strings and dimens since String and any Number seemed most relevant to verify.
- */
-@RunWith(Parameterized::class)
-class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
-
-    @get:Rule
-    private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
-
-    companion object {
-        @Parameterized.Parameters(name = "{1} {0}")
-        @JvmStatic
-        fun parameters(): Array<Any> {
-            val parameters = mutableListOf<Parameter>()
-
-            // Test resolution of resources encoded within the resources.arsc.
-            parameters += Parameter(
-                    "tableBased",
-                    query(mapOf(
-                            "getOverlaid" to { res ->
-                                res.getString(R.string.test)
-                            },
-                            "getAdditional" to { res ->
-                                res.getString(0x7f0400fe /* R.string.additional */)
-                            },
-                            "getIdentifier" to { res ->
-                                res.getString(res.getIdentifier("test", "string",
-                                        "android.content.res.loader.test"))
-                            },
-                            "getIdentifierAdditional" to { res ->
-                                res.getString(res.getIdentifier("additional", "string",
-                                        "android.content.res.loader.test"))
-                            }
-                    )),
-                    mapOf("getOverlaid" to "Not overlaid",
-                            "getAdditional" to "NotFoundException",
-                            "getIdentifier" to "Not overlaid",
-                            "getIdentifierAdditional" to "NotFoundException"),
-
-                    mapOf("getOverlaid" to "One",
-                            "getAdditional" to "One",
-                            "getIdentifier" to "One",
-                            "getIdentifierAdditional" to "One"),
-
-                    mapOf("getOverlaid" to "Two",
-                            "getAdditional" to "Two",
-                            "getIdentifier" to "Two",
-                            "getIdentifierAdditional" to "Two"),
-
-                    mapOf("getOverlaid" to "Three",
-                            "getAdditional" to "Three",
-                            "getIdentifier" to "Three",
-                            "getIdentifierAdditional" to "Three"),
-
-                    mapOf("getOverlaid" to "Four",
-                            "getAdditional" to "Four",
-                            "getIdentifier" to "Four",
-                            "getIdentifierAdditional" to "Four"),
-                    listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
-                            DataType.APK_RAM_OFFSETS, DataType.ARSC_DISK_FD,
-                            DataType.ARSC_DISK_FD_OFFSETS, DataType.ARSC_RAM_MEMORY,
-                            DataType.ARSC_RAM_MEMORY_OFFSETS, DataType.SPLIT, DataType.DIRECTORY)
-            )
-
-            // Test resolution of file-based resources and assets with no assets provider.
-            parameters += Parameter(
-                    "tableFileBased",
-                    query(mapOf(
-                            // Drawable xml in res directory
-                            "drawableXml" to { res ->
-                                (res.getDrawable(R.drawable.drawable_xml) as ColorDrawable)
-                                        .color.toString()
-                            },
-                            // Asset as compiled XML layout in res directory
-                            "layout" to { res ->
-                                res.getLayout(R.layout.layout).advanceToRoot().name
-                            },
-                            // Bitmap drawable in res directory
-                            "drawablePng" to { res ->
-                                (res.getDrawable(R.drawable.drawable_png) as BitmapDrawable)
-                                        .bitmap.getColor(0, 0).toArgb().toString()
-                            }
-                    )),
-                    mapOf("drawableXml" to Color.parseColor("#B2D2F2").toString(),
-                            "layout" to "MysteryLayout",
-                            "drawablePng" to Color.parseColor("#FF00FF").toString()),
-
-                    mapOf("drawableXml" to Color.parseColor("#000001").toString(),
-                            "layout" to "RelativeLayout",
-                            "drawablePng" to Color.RED.toString()),
-
-                    mapOf("drawableXml" to Color.parseColor("#000002").toString(),
-                            "layout" to "LinearLayout",
-                            "drawablePng" to Color.GREEN.toString()),
-
-                    mapOf("drawableXml" to Color.parseColor("#000003").toString(),
-                            "layout" to "FrameLayout",
-                            "drawablePng" to Color.BLUE.toString()),
-
-                    mapOf("drawableXml" to Color.parseColor("#000004").toString(),
-                            "layout" to "TableLayout",
-                            "drawablePng" to Color.WHITE.toString()),
-                    listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
-                            DataType.APK_RAM_OFFSETS, DataType.SPLIT, DataType.DIRECTORY)
-            )
-
-            // Test resolution of assets.
-            parameters += Parameter(
-                    "fileBased",
-                    query(mapOf(
-                            // File in the assets directory
-                            "openAsset" to { res ->
-                                res.assets.open("asset.txt").reader().readText()
-                            },
-                            // From assets directory returning file descriptor
-                            "openAssetFd" to { res ->
-                                res.assets.openFd("asset.txt").readText()
-                            },
-                            // Asset as compiled XML layout in res directory
-                            "layout" to { res ->
-                                res.assets.openXmlResourceParser("res/layout/layout.xml")
-                                        .advanceToRoot().name
-                            }
-                    )),
-                    mapOf("openAsset" to "In assets directory",
-                            "openAssetFd" to "In assets directory",
-                            "layout" to "MysteryLayout"),
-
-                    mapOf("openAsset" to "One",
-                            "openAssetFd" to "One",
-                            "layout" to "RelativeLayout"),
-
-                    mapOf("openAsset" to "Two",
-                            "openAssetFd" to "Two",
-                            "layout" to "LinearLayout"),
-
-                    mapOf("openAsset" to "Three",
-                            "openAssetFd" to "Three",
-                            "layout" to "FrameLayout"),
-
-                    mapOf("openAsset" to "Four",
-                            "openAssetFd" to "Four",
-                            "layout" to "TableLayout"),
-                    listOf(DataType.EMPTY)
-            )
-
-            // Test assets from apk and provider
-            parameters += Parameter(
-                    "fileBasedApkAssetsProvider",
-                    query(mapOf(
-                            // File in the assets directory
-                            "openAsset" to { res ->
-                                res.assets.open("asset.txt").reader().readText()
-                            },
-                            // From assets directory returning file descriptor
-                            "openAssetFd" to { res ->
-                                res.assets.openFd("asset.txt").readText()
-                            }
-                    )),
-                    mapOf("openAsset" to "In assets directory",
-                            "openAssetFd" to "In assets directory"),
-
-                    mapOf("openAsset" to "AssetsOne",
-                            "openAssetFd" to "AssetsOne"),
-                    { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt",
-                            "AssetsOne") },
-
-                    mapOf("openAsset" to "Two",
-                            "openAssetFd" to "Two"),
-                    null /* assetProviderTwo */,
-
-                    mapOf("openAsset" to "AssetsThree",
-                            "openAssetFd" to "AssetsThree"),
-                    { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt",
-                            "AssetsThree") },
-
-                    mapOf("openAsset" to "Four",
-                            "openAssetFd" to "Four"),
-                    null /* assetProviderFour */,
-                    listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
-                            DataType.APK_RAM_OFFSETS, DataType.DIRECTORY)
-
-            )
-
-            // TODO(151949807): Increase testing for cookie based APIs and for what happens when
-            // some providers do not overlay base resources
-
-            return parameters.flatMap { parameter ->
-                parameter.dataTypes.map { dataType ->
-                    arrayOf(dataType, parameter)
-                }
-            }.toTypedArray()
-        }
-    }
-
-    @Suppress("LateinitVarOverridesLateinitVar")
-    @field:Parameterized.Parameter(0)
-    override lateinit var dataType: DataType
-
-    @field:Parameterized.Parameter(1)
-    lateinit var parameter: Parameter
-
-    private val valueOriginal by lazy { mapToString(parameter.valueOriginal) }
-    private val valueOne by lazy { mapToString(parameter.valueOne) }
-    private val valueTwo by lazy { mapToString(parameter.valueTwo) }
-    private val valueThree by lazy { mapToString(parameter.valueThree) }
-    private val valueFour by lazy { mapToString(parameter.valueFour) }
-
-    private fun openOne() = PROVIDER_ONE.openProvider(dataType,
-            parameter.assetProviderOne?.invoke())
-    private fun openTwo() = PROVIDER_TWO.openProvider(dataType,
-            parameter.assetProviderTwo?.invoke())
-    private fun openThree() = PROVIDER_THREE.openProvider(dataType,
-            parameter.assetProviderThree?.invoke())
-    private fun openFour() = PROVIDER_FOUR.openProvider(dataType,
-            parameter.assetProviderFour?.invoke())
-    private fun openEmpty() = PROVIDER_EMPTY.openProvider(DataType.EMPTY, null)
-
-    // Class method for syntax highlighting purposes
-    private fun getValue(c: Context = context) = parameter.getValue(c.resources)
-    private fun getValue(r: Resources) = parameter.getValue(r)
-
-    @Test
-    fun assertValueUniqueness() {
-        // Ensure the parameters are valid in case of coding errors
-        val original = getValue()
-        assertEquals(valueOriginal, original)
-        assertNotEquals(valueOne, original)
-        assertNotEquals(valueTwo, original)
-        assertNotEquals(valueThree, original)
-        assertNotEquals(valueFour, original)
-        assertNotEquals(valueTwo, valueOne)
-        assertNotEquals(valueThree, valueOne)
-        assertNotEquals(valueFour, valueOne)
-        assertNotEquals(valueThree, valueTwo)
-        assertNotEquals(valueFour, valueTwo)
-        assertNotEquals(valueFour, valueThree)
-    }
-
-    @Test
-    fun addProvidersRepeatedly() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.addProvider(testOne)
-        assertEquals(valueOne, getValue())
-
-        loader.addProvider(testTwo)
-        assertEquals(valueTwo, getValue())
-
-        loader.removeProvider(testOne)
-        assertEquals(valueTwo, getValue())
-
-        loader.removeProvider(testTwo)
-        assertEquals(valueOriginal, getValue())
-    }
-
-    @Test
-    fun addLoadersRepeatedly() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader1 = ResourcesLoader()
-        val loader2 = ResourcesLoader()
-
-        resources.addLoaders(loader1)
-        loader1.addProvider(testOne)
-        assertEquals(valueOne, getValue())
-
-        resources.addLoaders(loader2)
-        loader2.addProvider(testTwo)
-        assertEquals(valueTwo, getValue())
-
-        resources.removeLoaders(loader1)
-        assertEquals(valueTwo, getValue())
-
-        resources.removeLoaders(loader2)
-        assertEquals(valueOriginal, getValue())
-    }
-
-    @Test
-    fun setMultipleProviders() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.providers = listOf(testOne, testTwo)
-        assertEquals(valueTwo, getValue())
-
-        loader.removeProvider(testTwo)
-        assertEquals(valueOne, getValue())
-
-        loader.providers = Collections.emptyList()
-        assertEquals(valueOriginal, getValue())
-    }
-
-    @Test
-    fun addMultipleLoaders() {
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(openOne())
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(openTwo())
-
-        resources.addLoaders(loader1, loader2)
-        assertEquals(valueTwo, getValue())
-
-        resources.removeLoaders(loader2)
-        assertEquals(valueOne, getValue())
-
-        resources.removeLoaders(loader1)
-        assertEquals(valueOriginal, getValue())
-    }
-
-    @Test
-    fun emptyProvider() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val testEmpty = openEmpty()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.providers = listOf(testOne, testEmpty, testTwo)
-        assertEquals(valueTwo, getValue())
-
-        loader.removeProvider(testTwo)
-        assertEquals(valueOne, getValue())
-
-        loader.removeProvider(testOne)
-        assertEquals(valueOriginal, getValue())
-
-        loader.providers = Collections.emptyList()
-        assertEquals(valueOriginal, getValue())
-    }
-
-    @Test(expected = UnsupportedOperationException::class)
-    fun getProvidersDoesNotLeakMutability() {
-        val testOne = openOne()
-        val loader = ResourcesLoader()
-        val providers = loader.providers
-        providers += testOne
-    }
-
-    @Test(expected = UnsupportedOperationException::class)
-    fun getLoadersDoesNotLeakMutability() {
-        val loaders = resources.loaders
-        loaders += ResourcesLoader()
-    }
-
-    @Test
-    fun alreadyAddedProviderNoOps() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.addProvider(testOne)
-        loader.addProvider(testTwo)
-        loader.addProvider(testOne)
-
-        assertEquals(2, loader.providers.size)
-        assertEquals(loader.providers[0], testOne)
-        assertEquals(loader.providers[1], testTwo)
-    }
-
-    @Test
-    fun alreadyAddedLoaderNoOps() {
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(openOne())
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(openTwo())
-
-        resources.addLoaders(loader1)
-        resources.addLoaders(loader2)
-        resources.addLoaders(loader1)
-
-        assertEquals(2, resources.loaders.size)
-        assertEquals(resources.loaders[0], loader1)
-        assertEquals(resources.loaders[1], loader2)
-    }
-
-    @Test
-    fun repeatedRemoveProviderNoOps() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.addProvider(testOne)
-        loader.addProvider(testTwo)
-
-        loader.removeProvider(testOne)
-        loader.removeProvider(testOne)
-
-        assertEquals(1, loader.providers.size)
-        assertEquals(loader.providers[0], testTwo)
-    }
-
-    @Test
-    fun repeatedRemoveLoaderNoOps() {
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(openOne())
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(openTwo())
-
-        resources.addLoaders(loader1, loader2)
-        resources.removeLoaders(loader1)
-        resources.removeLoaders(loader1)
-
-        assertEquals(1, resources.loaders.size)
-        assertEquals(resources.loaders[0], loader2)
-
-        resources.removeLoaders(loader2, loader2)
-
-        assertEquals(0, resources.loaders.size)
-    }
-
-    @Test
-    fun repeatedSetProvider() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.providers = listOf(testOne, testTwo)
-        loader.providers = listOf(testOne, testTwo)
-
-        assertEquals(2, loader.providers.size)
-        assertEquals(loader.providers[0], testOne)
-        assertEquals(loader.providers[1], testTwo)
-    }
-
-    @Test
-    fun repeatedAddMultipleLoaders() {
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(openOne())
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(openTwo())
-
-        resources.addLoaders(loader1, loader2)
-        resources.addLoaders(loader1, loader2)
-
-        assertEquals(2, resources.loaders.size)
-        assertEquals(resources.loaders[0], loader1)
-        assertEquals(resources.loaders[1], loader2)
-    }
-
-    @Test
-    fun reorderProviders() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.addProvider(testOne)
-        loader.addProvider(testTwo)
-        assertEquals(valueTwo, getValue())
-
-        loader.removeProvider(testOne)
-        assertEquals(valueTwo, getValue())
-
-        loader.addProvider(testOne)
-        assertEquals(valueOne, getValue())
-
-        loader.removeProvider(testTwo)
-        assertEquals(valueOne, getValue())
-
-        loader.removeProvider(testOne)
-        assertEquals(valueOriginal, getValue())
-    }
-
-    @Test
-    fun reorderLoaders() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(testOne)
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(testTwo)
-
-        resources.addLoaders(loader1)
-        resources.addLoaders(loader2)
-        assertEquals(valueTwo, getValue())
-
-        resources.removeLoaders(loader1)
-        assertEquals(valueTwo, getValue())
-
-        resources.addLoaders(loader1)
-        assertEquals(valueOne, getValue())
-
-        resources.removeLoaders(loader2)
-        assertEquals(valueOne, getValue())
-
-        resources.removeLoaders(loader1)
-        assertEquals(valueOriginal, getValue())
-    }
-
-    @Test
-    fun reorderMultipleLoadersAndProviders() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val testThree = openThree()
-        val testFour = openFour()
-
-        val loader1 = ResourcesLoader()
-        loader1.providers = listOf(testOne, testTwo)
-
-        val loader2 = ResourcesLoader()
-        loader2.providers = listOf(testThree, testFour)
-
-        resources.addLoaders(loader1, loader2)
-        assertEquals(valueFour, getValue())
-
-        resources.removeLoaders(loader1)
-        resources.addLoaders(loader1)
-        assertEquals(valueTwo, getValue())
-
-        loader1.removeProvider(testTwo)
-        assertEquals(valueOne, getValue())
-
-        loader1.removeProvider(testOne)
-        assertEquals(valueFour, getValue())
-
-        loader2.removeProvider(testFour)
-        assertEquals(valueThree, getValue())
-    }
-
-    private fun createContext(context: Context, id: Int): Context {
-        val overrideConfig = Configuration()
-        overrideConfig.orientation = Int.MAX_VALUE - id
-        return context.createConfigurationContext(overrideConfig)
-    }
-
-    @Test
-    fun copyContextLoaders() {
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(openOne())
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(openTwo())
-
-        resources.addLoaders(loader1)
-        assertEquals(valueOne, getValue())
-
-        // The child context should include the loaders of the original context.
-        val childContext = createContext(context, 0)
-        assertEquals(valueOne, getValue(childContext))
-
-        // Changing the loaders of the child context should not affect the original context.
-        childContext.resources.addLoaders(loader2)
-        assertEquals(valueOne, getValue())
-        assertEquals(valueTwo, getValue(childContext))
-
-        // Changing the loaders of the original context should not affect the child context.
-        resources.removeLoaders(loader1)
-        assertEquals(valueOriginal, getValue())
-        assertEquals(valueTwo, getValue(childContext))
-
-        // A new context created from the original after an update to the original's loaders should
-        // have the updated loaders.
-        val originalPrime = createContext(context, 2)
-        assertEquals(valueOriginal, getValue(originalPrime))
-
-        // A new context created from the child context after an update to the child's loaders
-        // should have the updated loaders.
-        val childPrime = createContext(childContext, 1)
-        assertEquals(valueTwo, getValue(childPrime))
-    }
-
-    @Test
-    fun loaderUpdatesAffectContexts() {
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        resources.addLoaders(loader)
-        loader.addProvider(testOne)
-        assertEquals(valueOne, getValue())
-
-        val childContext = createContext(context, 0)
-        assertEquals(valueOne, getValue(childContext))
-
-        // Adding a provider to a loader affects all contexts that use the loader.
-        loader.addProvider(testTwo)
-        assertEquals(valueTwo, getValue())
-        assertEquals(valueTwo, getValue(childContext))
-
-        // Changes to the loaders for a context do not affect providers.
-        resources.clearLoaders()
-        assertEquals(valueOriginal, getValue())
-        assertEquals(valueTwo, getValue(childContext))
-
-        val childContext2 = createContext(context, 1)
-        assertEquals(valueOriginal, getValue())
-        assertEquals(valueOriginal, getValue(childContext2))
-
-        childContext2.resources.addLoaders(loader)
-        assertEquals(valueOriginal, getValue())
-        assertEquals(valueTwo, getValue(childContext))
-        assertEquals(valueTwo, getValue(childContext2))
-    }
-
-    @Test
-    fun appLoadersIncludedInActivityContexts() {
-        val loader = ResourcesLoader()
-        loader.addProvider(openOne())
-
-        val applicationContext = context.applicationContext
-        applicationContext.resources.addLoaders(loader)
-        assertEquals(valueOne, getValue(applicationContext))
-
-        val activity = mTestActivityRule.launchActivity(Intent())
-        assertEquals(valueOne, getValue(activity))
-
-        applicationContext.resources.clearLoaders()
-    }
-
-    @Test
-    fun loadersApplicationInfoChanged() {
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(openOne())
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(openTwo())
-
-        val applicationContext = context.applicationContext
-        applicationContext.resources.addLoaders(loader1)
-        assertEquals(valueOne, getValue(applicationContext))
-
-        var token: IBinder? = null
-        val activity = mTestActivityRule.launchActivity(Intent())
-        mTestActivityRule.runOnUiThread(Runnable {
-            token = activity.activityToken
-            val at = activity.activityThread
-
-            // The activity should have the loaders from the application.
-            assertEquals(valueOne, getValue(applicationContext))
-            assertEquals(valueOne, getValue(activity))
-
-            activity.resources.addLoaders(loader2)
-            assertEquals(valueOne, getValue(applicationContext))
-            assertEquals(valueTwo, getValue(activity))
-
-            // Relaunches the activity.
-            at.handleApplicationInfoChanged(activity.applicationInfo)
-        })
-
-        mTestActivityRule.runOnUiThread(Runnable {
-            val activityThread = activity.activityThread
-            val newActivity = activityThread.getActivity(token)
-
-            // The loader added to the activity loaders should not be persisted.
-            assertEquals(valueOne, getValue(applicationContext))
-            assertEquals(valueOne, getValue(newActivity))
-        })
-
-        applicationContext.resources.clearLoaders()
-    }
-
-    @Test
-    fun multipleLoadersHaveSameProviders() {
-        val provider1 = openOne()
-        val loader1 = ResourcesLoader()
-        loader1.addProvider(provider1)
-        val loader2 = ResourcesLoader()
-        loader2.addProvider(provider1)
-        loader2.addProvider(openTwo())
-
-        resources.addLoaders(loader1, loader2)
-        assertEquals(valueTwo, getValue())
-
-        resources.removeLoaders(loader1)
-        resources.addLoaders(loader1)
-        assertEquals(valueOne, getValue())
-
-        assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun cannotUseClosedProvider() {
-        val provider = openOne()
-        provider.close()
-        val loader = ResourcesLoader()
-        loader.addProvider(provider)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun cannotCloseUsedProvider() {
-        val provider = openOne()
-        val loader = ResourcesLoader()
-        loader.addProvider(provider)
-        provider.close()
-    }
-
-    @Test
-    fun addLoadersRepeatedlyCustomResources() {
-        val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics,
-                resources.configuration!!)
-        val originalValue = getValue(res)
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader1 = ResourcesLoader()
-        val loader2 = ResourcesLoader()
-
-        res.addLoaders(loader1)
-        loader1.addProvider(testOne)
-        assertEquals(valueOne, getValue(res))
-
-        res.addLoaders(loader2)
-        loader2.addProvider(testTwo)
-        assertEquals(valueTwo, getValue(res))
-
-        res.removeLoaders(loader1)
-        res.addLoaders(loader1)
-        assertEquals(valueOne, getValue(res))
-
-        res.removeLoaders(loader1)
-        assertEquals(valueTwo, getValue(res))
-
-        res.removeLoaders(loader2)
-        assertEquals(originalValue, getValue(res))
-    }
-
-    @Test
-    fun setMultipleProvidersCustomResources() {
-        val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics,
-                resources.configuration!!)
-        val originalValue = getValue(res)
-        val testOne = openOne()
-        val testTwo = openTwo()
-        val loader = ResourcesLoader()
-
-        res.addLoaders(loader)
-        loader.providers = listOf(testOne, testTwo)
-        assertEquals(valueTwo, getValue(res))
-
-        loader.removeProvider(testTwo)
-        assertEquals(valueOne, getValue(res))
-
-        loader.providers = Collections.emptyList()
-        assertEquals(originalValue, getValue(res))
-    }
-
-    data class Parameter(
-        val testPrefix: String,
-        val getValue: Resources.() -> String,
-        val valueOriginal: Map<String, String>,
-        val valueOne: Map<String, String>,
-        val assetProviderOne: (() -> MemoryAssetsProvider)? = null,
-        val valueTwo: Map<String, String>,
-        val assetProviderTwo: (() -> MemoryAssetsProvider)? = null,
-        val valueThree: Map<String, String>,
-        val assetProviderThree: (() -> MemoryAssetsProvider)? = null,
-        val valueFour: Map<String, String>,
-        val assetProviderFour: (() -> MemoryAssetsProvider)? = null,
-        val dataTypes: List<DataType>
-    ) {
-        constructor(
-            testPrefix: String,
-            getValue: Resources.() -> String,
-            valueOriginal: Map<String, String>,
-            valueOne: Map<String, String>,
-            valueTwo: Map<String, String>,
-            valueThree: Map<String, String>,
-            valueFour: Map<String, String>,
-            dataTypes: List<DataType>
-        ): this(testPrefix, getValue, valueOriginal, valueOne,
-                null, valueTwo, null, valueThree, null, valueFour, null, dataTypes)
-
-        override fun toString() = testPrefix
-    }
-}
-
-class TestActivity : Activity()
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
deleted file mode 100644
index 526160d..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res.loader.test
-
-import android.content.Context
-import android.content.res.AssetFileDescriptor
-import android.content.res.Resources
-import android.os.ParcelFileDescriptor
-import android.util.TypedValue
-import org.mockito.Answers
-import org.mockito.stubbing.Answer
-import org.xmlpull.v1.XmlPullParser
-import java.io.File
-
-object Utils {
-    val ANSWER_THROWS = Answer<Any> {
-        when (val name = it.method.name) {
-            "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
-            else -> throw UnsupportedOperationException("$name with " +
-                    "${it.arguments?.joinToString()} should not be called")
-        }
-    }
-}
-
-fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension(
-        TypedValue.COMPLEX_UNIT_DIP,
-        this.toFloat(),
-        resources.displayMetrics
-).toInt()
-
-fun AssetFileDescriptor.readText() = createInputStream().reader().readText()
-
-fun XmlPullParser.advanceToRoot() = apply {
-    while (next() != XmlPullParser.START_TAG) {
-        // Empty
-    }
-}
-
-fun Context.copiedAssetFile(fileName: String): ParcelFileDescriptor {
-    return resources.assets.open(fileName).use { input ->
-        // AssetManager doesn't expose a direct file descriptor to the asset, so copy it to
-        // an individual file so one can be created manually.
-        val copiedFile = File(filesDir, fileName)
-        copiedFile.outputStream().use { output ->
-            input.copyTo(output)
-        }
-        ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE)
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
new file mode 100644
index 0000000..374edb8
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
@@ -0,0 +1,123 @@
+/*
+ * 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.internal.app;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChooserActivityLoggerFake implements ChooserActivityLogger {
+    static class CallRecord {
+        // shared fields between all logs
+        public int atomId;
+        public String packageName;
+        public InstanceId instanceId;
+
+        // generic log field
+        public UiEventLogger.UiEventEnum event;
+
+        // share started fields
+        public String mimeType;
+        public int appProvidedDirect;
+        public int appProvidedApp;
+        public boolean isWorkprofile;
+        public int previewType;
+        public String intent;
+
+        // share completed fields
+        public int targetType;
+        public int positionPicked;
+
+        CallRecord(int atomId, UiEventLogger.UiEventEnum eventId,
+                String packageName, InstanceId instanceId) {
+            this.atomId = atomId;
+            this.packageName = packageName;
+            this.instanceId = instanceId;
+            this.event = eventId;
+        }
+
+        CallRecord(int atomId, String packageName, InstanceId instanceId, String mimeType,
+                int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+                String intent) {
+            this.atomId = atomId;
+            this.packageName = packageName;
+            this.instanceId = instanceId;
+            this.mimeType = mimeType;
+            this.appProvidedDirect = appProvidedDirect;
+            this.appProvidedApp = appProvidedApp;
+            this.isWorkprofile = isWorkprofile;
+            this.previewType = previewType;
+            this.intent = intent;
+        }
+
+        CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType,
+                int positionPicked) {
+            this.atomId = atomId;
+            this.packageName = packageName;
+            this.instanceId = instanceId;
+            this.targetType = targetType;
+            this.positionPicked = positionPicked;
+        }
+
+    }
+    private List<CallRecord> mCalls = new ArrayList<>();
+
+    public int numCalls() {
+        return mCalls.size();
+    }
+
+    List<CallRecord> getCalls() {
+        return mCalls;
+    }
+
+    CallRecord get(int index) {
+        return mCalls.get(index);
+    }
+
+    UiEventLogger.UiEventEnum event(int index) {
+        return mCalls.get(index).event;
+    }
+
+    @Override
+    public void logShareStarted(int eventId, String packageName, String mimeType,
+            int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+            String intent) {
+        mCalls.add(new CallRecord(FrameworkStatsLog.SHARESHEET_STARTED, packageName,
+                getInstanceId(), mimeType, appProvidedDirect, appProvidedApp, isWorkprofile,
+                previewType, intent));
+    }
+
+    @Override
+    public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+        mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(),
+                SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked));
+    }
+
+    @Override
+    public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
+        mCalls.add(new CallRecord(FrameworkStatsLog.UI_EVENT_REPORTED,
+                    event, "", instanceId));
+    }
+
+    @Override
+    public InstanceId getInstanceId() {
+        return InstanceId.fakeInstanceId(-1);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 812e2a6..bc0cdc1 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -34,6 +34,8 @@
 import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
 import static com.android.internal.app.MatcherUtils.first;
 
+import static junit.framework.Assert.assertTrue;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
@@ -67,6 +69,7 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.service.chooser.ChooserTarget;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -75,8 +78,10 @@
 import com.android.internal.R;
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -140,6 +145,10 @@
     public void cleanOverrideData() {
         sOverrides.reset();
         sOverrides.createPackageManager = mPackageManagerOverride;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
+                Boolean.toString(false),
+                true /* makeDefault*/);
     }
 
     @Test
@@ -988,7 +997,7 @@
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET,
                         directShareToShortcutInfos,
-                        null)
+                        List.of())
         );
 
         // Thread.sleep shouldn't be a thing in an integration test but it's
@@ -1060,7 +1069,7 @@
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET,
                         directShareToShortcutInfos,
-                        null)
+                        List.of())
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -1148,7 +1157,7 @@
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET,
                         directShareToShortcutInfos,
-                        null)
+                        List.of())
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -1430,6 +1439,299 @@
                 .check(matches(isDisplayed()));
     }
 
+    @Test
+    public void testAppTargetLogging() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        assertThat(activity.getAdapter().getCount(), is(2));
+        onView(withId(R.id.profile_button)).check(doesNotExist());
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+        onView(withText(toChoose.activityInfo.name))
+                .perform(click());
+        waitForIdle();
+
+        ChooserActivityLoggerFake logger =
+                (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+        assertThat(logger.numCalls(), is(6));
+        // first one should be SHARESHEET_TRIGGERED uievent
+        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(0).event.getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+        // second one should be SHARESHEET_STARTED event
+        assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+        assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+        assertThat(logger.get(1).mimeType, is("text/plain"));
+        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).appProvidedApp, is(0));
+        assertThat(logger.get(1).appProvidedDirect, is(0));
+        assertThat(logger.get(1).isWorkprofile, is(false));
+        assertThat(logger.get(1).previewType, is(3));
+        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(2).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+        // fourth and fifth are just artifacts of test set-up
+        // sixth one should be ranking atom with SHARESHEET_APP_TARGET_SELECTED event
+        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(5).targetType,
+                is(ChooserActivityLogger
+                        .SharesheetTargetSelectedEvent.SHARESHEET_APP_TARGET_SELECTED.getId()));
+    }
+
+    @Test
+    public void testDirectTargetLogging() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        // Create direct share target
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+        // Start activity
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+
+        // Insert the direct share target
+        Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+        directShareToShortcutInfos.put(serviceTargets.get(0), null);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> activity.getAdapter().addServiceResults(
+                        activity.createTestDisplayResolveInfo(sendIntent,
+                                ri,
+                                "testLabel",
+                                "testInfo",
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
+                        serviceTargets,
+                        TARGET_TYPE_CHOOSER_TARGET,
+                        directShareToShortcutInfos,
+                        null)
+        );
+        // Thread.sleep shouldn't be a thing in an integration test but it's
+        // necessary here because of the way the code is structured
+        // TODO: restructure the tests b/129870719
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+        assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
+                activity.getAdapter().getCount(), is(3));
+        assertThat("Chooser should have exactly one selectable direct target",
+                activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+        assertThat("The resolver info must match the resolver info used to create the target",
+                activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+
+        // Click on the direct target
+        String name = serviceTargets.get(0).getTitle().toString();
+        onView(withText(name))
+                .perform(click());
+        waitForIdle();
+
+        ChooserActivityLoggerFake logger =
+                (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+        assertThat(logger.numCalls(), is(6));
+        // first one should be SHARESHEET_TRIGGERED uievent
+        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(0).event.getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+        // second one should be SHARESHEET_STARTED event
+        assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+        assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+        assertThat(logger.get(1).mimeType, is("text/plain"));
+        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).appProvidedApp, is(0));
+        assertThat(logger.get(1).appProvidedDirect, is(0));
+        assertThat(logger.get(1).isWorkprofile, is(false));
+        assertThat(logger.get(1).previewType, is(3));
+        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(2).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+        // fourth and fifth are just artifacts of test set-up
+        // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
+        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(5).targetType,
+                is(ChooserActivityLogger
+                        .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()));
+    }
+
+    @Test
+    public void testCopyTextToClipboardLogging() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.chooser_copy_button)).perform(click());
+
+        ChooserActivityLoggerFake logger =
+                (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+        assertThat(logger.numCalls(), is(6));
+        // first one should be SHARESHEET_TRIGGERED uievent
+        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(0).event.getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+        // second one should be SHARESHEET_STARTED event
+        assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+        assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+        assertThat(logger.get(1).mimeType, is("text/plain"));
+        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).appProvidedApp, is(0));
+        assertThat(logger.get(1).appProvidedDirect, is(0));
+        assertThat(logger.get(1).isWorkprofile, is(false));
+        assertThat(logger.get(1).previewType, is(3));
+        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(2).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+        // fourth and fifth are just artifacts of test set-up
+        // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
+        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(5).targetType,
+                is(ChooserActivityLogger
+                        .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
+    }
+
+    @Test
+    public void testSwitchProfileLogging() throws InterruptedException {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+
+        final ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        onView(withText(R.string.resolver_personal_tab)).perform(click());
+        waitForIdle();
+
+        ChooserActivityLoggerFake logger =
+                (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+        assertThat(logger.numCalls(), is(8));
+        // first one should be SHARESHEET_TRIGGERED uievent
+        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(0).event.getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+        // second one should be SHARESHEET_STARTED event
+        assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+        assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+        assertThat(logger.get(1).mimeType, is("TestType"));
+        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).appProvidedApp, is(0));
+        assertThat(logger.get(1).appProvidedDirect, is(0));
+        assertThat(logger.get(1).isWorkprofile, is(false));
+        assertThat(logger.get(1).previewType, is(3));
+        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(2).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+        // fourth one is artifact of test setup
+        // fifth one is switch to work profile
+        assertThat(logger.get(4).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(4).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+        // sixth one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(5).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+        // seventh one is artifact of test setup
+        // eigth one is switch to work profile
+        assertThat(logger.get(7).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(7).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+    }
+
+    @Test
+    public void testAutolaunch_singleTarget_wifthWorkProfileAndTabbedViewOff_noAutolaunch() {
+        ResolverActivity.ENABLE_TABBED_VIEW = false;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+        waitForIdle();
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        assertTrue(chosen[0] == null);
+    }
+
+    @Test
+    public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() {
+        ResolverActivity.ENABLE_TABBED_VIEW = false;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(1);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+        waitForIdle();
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
     private Intent createSendTextIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 363551b..5b83f95 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -146,6 +146,11 @@
     }
 
     @Override
+    protected ChooserActivityLogger getChooserActivityLogger() {
+        return sOverrides.chooserActivityLogger;
+    }
+
+    @Override
     public Cursor queryResolver(ContentResolver resolver, Uri uri) {
         if (sOverrides.resolverCursor != null) {
             return sOverrides.resolverCursor;
@@ -205,6 +210,7 @@
         public boolean resolverForceException;
         public Bitmap previewThumbnail;
         public MetricsLogger metricsLogger;
+        public ChooserActivityLogger chooserActivityLogger;
         public int alternateProfileSetting;
         public Resources resources;
         public UserHandle workProfileUserHandle;
@@ -223,6 +229,7 @@
             resolverListController = mock(ResolverListController.class);
             workResolverListController = mock(ResolverListController.class);
             metricsLogger = mock(MetricsLogger.class);
+            chooserActivityLogger = new ChooserActivityLoggerFake();
             alternateProfileSetting = 0;
             resources = null;
             workProfileUserHandle = null;
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 5a3aff9..0bf8663 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,8 @@
 import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
 import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
 
+import static junit.framework.Assert.assertTrue;
+
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -710,6 +712,54 @@
                 .check(matches(isDisplayed()));
     }
 
+    @Test
+    public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() {
+        ResolverActivity.ENABLE_TABBED_VIEW = false;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+        waitForIdle();
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertTrue(chosen[0] == null);
+    }
+
+    @Test
+    public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() {
+        ResolverActivity.ENABLE_TABBED_VIEW = false;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(1);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+        waitForIdle();
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
     private Intent createSendImageIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
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/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING
index d1a6a5c..777aa0b 100644
--- a/libs/androidfw/TEST_MAPPING
+++ b/libs/androidfw/TEST_MAPPING
@@ -5,7 +5,7 @@
       "host": true
     },
     {
-      "name": "FrameworksResourceLoaderTests"
+      "name": "CtsResourcesLoaderTests"
     }
   ]
 }
\ No newline at end of file
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index afd82ac..43cc4f2 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -50,10 +50,8 @@
 }
 
 SkAlphaType ImageDecoder::getOutAlphaType() const {
-    // While an SkBitmap may want to use kOpaque_SkAlphaType for a performance
-    // optimization, this class just outputs raw pixels. Using either
-    // premultiplication choice has no effect on decoding an opaque encoded image.
-    return mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
+    return opaque() ? kOpaque_SkAlphaType
+                    : mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
 }
 
 bool ImageDecoder::setTargetSize(int width, int height) {
@@ -82,8 +80,7 @@
     SkISize targetSize = { width, height }, decodeSize = targetSize;
     int sampleSize = mCodec->computeSampleSize(&decodeSize);
 
-    if (decodeSize != targetSize && mUnpremultipliedRequired
-            && !mCodec->getInfo().isOpaque()) {
+    if (decodeSize != targetSize && mUnpremultipliedRequired && !opaque()) {
         return false;
     }
 
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index b6b3785..41d939b 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -305,9 +305,6 @@
     }
 
     SkImageInfo bitmapInfo = decoder->getOutputInfo();
-    if (decoder->opaque()) {
-        bitmapInfo = bitmapInfo.makeAlphaType(kOpaque_SkAlphaType);
-    }
     if (asAlphaMask && colorType == kGray_8_SkColorType) {
         bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
     }
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index f3e4d81..4dd1a29 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -71,8 +71,7 @@
  * heavy-weight work after receiving an update - such as using the network.
  *
  * <p>Activities should strongly consider removing all location
- * request when entering the background
- * (for example at {@link android.app.Activity#onPause}), or
+ * request when entering the background, or
  * at least swap the request to a larger interval and lower quality.
  * Future version of the location manager may automatically perform background
  * throttling on behalf of applications.
@@ -146,38 +145,32 @@
      */
     public static final int POWER_HIGH = 203;
 
-    /**
-     * By default, mFastestInterval = FASTEST_INTERVAL_MULTIPLE * mInterval
-     */
+    private static final long DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
     private static final double FASTEST_INTERVAL_FACTOR = 6.0;  // 6x
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private int mQuality = POWER_LOW;
     @UnsupportedAppUsage
-    private long mInterval = 60 * 60 * 1000;   // 60 minutes
+    private String mProvider;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private long mFastestInterval = (long) (mInterval / FASTEST_INTERVAL_FACTOR);  // 10 minutes
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private boolean mExplicitFastestInterval = false;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private long mExpireAt = Long.MAX_VALUE;  // no expiry
-    private long mExpireIn = Long.MAX_VALUE;  // no expiry
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private int mNumUpdates = Integer.MAX_VALUE;  // no expiry
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private float mSmallestDisplacement = 0.0f;    // meters
+    private int mQuality;
     @UnsupportedAppUsage
-    private WorkSource mWorkSource = null;
+    private long mInterval;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private long mFastestInterval;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private boolean mExplicitFastestInterval;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private long mExpireAt;
+    private long mExpireIn;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private int mNumUpdates;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private float mSmallestDisplacement;
     @UnsupportedAppUsage
-    private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
-    private boolean mLocationSettingsIgnored = false;
-
+    private boolean mHideFromAppOps;
+    private boolean mLocationSettingsIgnored;
+    private boolean mLowPowerMode;
     @UnsupportedAppUsage
-    private String mProvider = LocationManager.FUSED_PROVIDER;
-            // for deprecated APIs that explicitly request a provider
-
-    /** If true, GNSS chipset will make strong tradeoffs to substantially restrict power use */
-    private boolean mLowPowerMode = false;
+    private @Nullable WorkSource mWorkSource;
 
     /**
      * Create a location request with default parameters.
@@ -260,23 +253,71 @@
 
     /** @hide */
     public LocationRequest() {
+        this(
+                /* provider= */ LocationManager.FUSED_PROVIDER,
+                /* quality= */ POWER_LOW,
+                /* interval= */ DEFAULT_INTERVAL_MS,
+                /* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR),
+                /* explicitFastestInterval= */ false,
+                /* expireAt= */ Long.MAX_VALUE,
+                /* expireIn= */ Long.MAX_VALUE,
+                /* numUpdates= */ Integer.MAX_VALUE,
+                /* smallestDisplacement= */ 0,
+                /* hideFromAppOps= */ false,
+                /* lowPowerMode= */ false,
+                /* locationSettingsIgnored= */ false,
+                /* workSource= */ null);
     }
 
     /** @hide */
     public LocationRequest(LocationRequest src) {
-        mQuality = src.mQuality;
-        mInterval = src.mInterval;
-        mFastestInterval = src.mFastestInterval;
-        mExplicitFastestInterval = src.mExplicitFastestInterval;
-        mExpireAt = src.mExpireAt;
-        mExpireIn = src.mExpireIn;
-        mNumUpdates = src.mNumUpdates;
-        mSmallestDisplacement = src.mSmallestDisplacement;
-        mProvider = src.mProvider;
-        mWorkSource = src.mWorkSource;
-        mHideFromAppOps = src.mHideFromAppOps;
-        mLowPowerMode = src.mLowPowerMode;
-        mLocationSettingsIgnored = src.mLocationSettingsIgnored;
+        this(
+                src.mProvider,
+                src.mQuality,
+                src.mInterval,
+                src.mFastestInterval,
+                src.mExplicitFastestInterval,
+                src.mExpireAt,
+                src.mExpireIn,
+                src.mNumUpdates,
+                src.mSmallestDisplacement,
+                src.mHideFromAppOps,
+                src.mLowPowerMode,
+                src.mLocationSettingsIgnored,
+                src.mWorkSource);
+    }
+
+    private LocationRequest(
+            @NonNull String provider,
+            int quality,
+            long intervalMs,
+            long fastestIntervalMs,
+            boolean explicitFastestInterval,
+            long expireAt,
+            long expireInMs,
+            int numUpdates,
+            float smallestDisplacementM,
+            boolean hideFromAppOps,
+            boolean locationSettingsIgnored,
+            boolean lowPowerMode,
+            WorkSource workSource) {
+        Preconditions.checkArgument(provider != null, "invalid provider: null");
+        checkQuality(quality);
+
+        mProvider = provider;
+        mQuality = quality;
+        mInterval = intervalMs;
+        mFastestInterval = fastestIntervalMs;
+        mExplicitFastestInterval = explicitFastestInterval;
+        mExpireAt = expireAt;
+        mExpireIn = expireInMs;
+        mNumUpdates = numUpdates;
+        mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0,
+                Float.MAX_VALUE, "smallestDisplacementM");
+        mHideFromAppOps = hideFromAppOps;
+        mLowPowerMode = lowPowerMode;
+        mLocationSettingsIgnored = locationSettingsIgnored;
+        mWorkSource = workSource;
     }
 
     /**
@@ -567,7 +608,7 @@
 
     /** Sets the provider to use for this location request. */
     public @NonNull LocationRequest setProvider(@NonNull String provider) {
-        checkProvider(provider);
+        Preconditions.checkArgument(provider != null, "invalid provider: null");
         mProvider = provider;
         return this;
     }
@@ -580,9 +621,9 @@
 
     /** @hide */
     @SystemApi
-    public @NonNull LocationRequest setSmallestDisplacement(float meters) {
-        checkDisplacement(meters);
-        mSmallestDisplacement = meters;
+    public @NonNull LocationRequest setSmallestDisplacement(float smallestDisplacementM) {
+        mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0,
+                Float.MAX_VALUE, "smallestDisplacementM");
         return this;
     }
 
@@ -653,40 +694,24 @@
         }
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private static void checkDisplacement(float meters) {
-        if (meters < 0.0f) {
-            throw new IllegalArgumentException("invalid displacement: " + meters);
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private static void checkProvider(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("invalid provider: null");
-        }
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<LocationRequest> CREATOR =
+    public static final @NonNull Parcelable.Creator<LocationRequest> CREATOR =
             new Parcelable.Creator<LocationRequest>() {
                 @Override
                 public LocationRequest createFromParcel(Parcel in) {
-                    LocationRequest request = new LocationRequest();
-                    request.setQuality(in.readInt());
-                    request.setFastestInterval(in.readLong());
-                    request.setInterval(in.readLong());
-                    request.setExpireAt(in.readLong());
-                    request.setExpireIn(in.readLong());
-                    request.setNumUpdates(in.readInt());
-                    request.setSmallestDisplacement(in.readFloat());
-                    request.setHideFromAppOps(in.readInt() != 0);
-                    request.setLowPowerMode(in.readInt() != 0);
-                    request.setLocationSettingsIgnored(in.readInt() != 0);
-                    String provider = in.readString();
-                    if (provider != null) request.setProvider(provider);
-                    WorkSource workSource = in.readParcelable(null);
-                    if (workSource != null) request.setWorkSource(workSource);
-                    return request;
+                    return new LocationRequest(
+                            /* provider= */ in.readString(),
+                            /* quality= */ in.readInt(),
+                            /* interval= */ in.readLong(),
+                            /* fastestInterval= */ in.readLong(),
+                            /* explicitFastestInterval= */ in.readBoolean(),
+                            /* expireAt= */ in.readLong(),
+                            /* expireIn= */ in.readLong(),
+                            /* numUpdates= */ in.readInt(),
+                            /* smallestDisplacement= */ in.readFloat(),
+                            /* hideFromAppOps= */ in.readBoolean(),
+                            /* locationSettingsIgnored= */ in.readBoolean(),
+                            /* lowPowerMode= */ in.readBoolean(),
+                            /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
                 }
 
                 @Override
@@ -702,18 +727,19 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mProvider);
         parcel.writeInt(mQuality);
-        parcel.writeLong(mFastestInterval);
         parcel.writeLong(mInterval);
+        parcel.writeLong(mFastestInterval);
+        parcel.writeBoolean(mExplicitFastestInterval);
         parcel.writeLong(mExpireAt);
         parcel.writeLong(mExpireIn);
         parcel.writeInt(mNumUpdates);
         parcel.writeFloat(mSmallestDisplacement);
-        parcel.writeInt(mHideFromAppOps ? 1 : 0);
-        parcel.writeInt(mLowPowerMode ? 1 : 0);
-        parcel.writeInt(mLocationSettingsIgnored ? 1 : 0);
-        parcel.writeString(mProvider);
-        parcel.writeParcelable(mWorkSource, 0);
+        parcel.writeBoolean(mHideFromAppOps);
+        parcel.writeBoolean(mLocationSettingsIgnored);
+        parcel.writeBoolean(mLowPowerMode);
+        parcel.writeTypedObject(mWorkSource, 0);
     }
 
     /** @hide */
@@ -740,16 +766,19 @@
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
-        s.append("Request[").append(qualityToString(mQuality));
-        if (mProvider != null) s.append(' ').append(mProvider);
+        s.append("Request[");
+        s.append(qualityToString(mQuality));
+        s.append(" ").append(mProvider);
         if (mQuality != POWER_NONE) {
-            s.append(" requested=");
+            s.append(" interval=");
             TimeUtils.formatDuration(mInterval, s);
+            if (mExplicitFastestInterval) {
+                s.append(" fastestInterval=");
+                TimeUtils.formatDuration(mFastestInterval, s);
+            }
         }
-        s.append(" fastest=");
-        TimeUtils.formatDuration(mFastestInterval, s);
         if (mExpireAt != Long.MAX_VALUE) {
-            s.append(" expireAt=").append(TimeUtils.formatUptime(mExpireAt));
+            s.append(" expireAt=").append(TimeUtils.formatRealtime(mExpireAt));
         }
         if (mExpireIn != Long.MAX_VALUE) {
             s.append(" expireIn=");
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index 572fbc3..a81ddfe 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -23,7 +23,6 @@
 import android.os.WorkSource;
 import android.util.TimeUtils;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -83,18 +82,14 @@
             new Parcelable.Creator<ProviderRequest>() {
                 @Override
                 public ProviderRequest createFromParcel(Parcel in) {
-                    boolean reportLocation = in.readInt() == 1;
-                    long interval = in.readLong();
-                    boolean lowPowerMode = in.readBoolean();
-                    boolean locationSettingsIgnored = in.readBoolean();
-                    int count = in.readInt();
-                    ArrayList<LocationRequest> locationRequests = new ArrayList<>(count);
-                    for (int i = 0; i < count; i++) {
-                        locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
-                    }
-                    WorkSource workSource = in.readParcelable(null);
-                    return new ProviderRequest(reportLocation, interval, lowPowerMode,
-                            locationSettingsIgnored, locationRequests, workSource);
+                    return new ProviderRequest(
+                            /* reportLocation= */ in.readBoolean(),
+                            /* interval= */ in.readLong(),
+                            /* lowPowerMode= */ in.readBoolean(),
+                            /* locationSettingsIgnored= */ in.readBoolean(),
+                            /* locationRequests= */
+                            in.createTypedArrayList(LocationRequest.CREATOR),
+                            /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
                 }
 
                 @Override
@@ -110,15 +105,12 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeInt(reportLocation ? 1 : 0);
+        parcel.writeBoolean(reportLocation);
         parcel.writeLong(interval);
         parcel.writeBoolean(lowPowerMode);
         parcel.writeBoolean(locationSettingsIgnored);
-        parcel.writeInt(locationRequests.size());
-        for (LocationRequest request : locationRequests) {
-            request.writeToParcel(parcel, flags);
-        }
-        parcel.writeParcelable(workSource, flags);
+        parcel.writeTypedList(locationRequests);
+        parcel.writeTypedObject(workSource, flags);
     }
 
     @Override
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index f5b0806..0ab62c1 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -99,11 +99,11 @@
         if (role != ROLE_OUTPUT && role != ROLE_INPUT) {
             throw new IllegalArgumentException("Invalid role " + role);
         }
-        if (role == ROLE_OUTPUT && !AudioDeviceInfo.isValidAudioDeviceTypeOut(type)) {
-            throw new IllegalArgumentException("Invalid output device type " + type);
+        if (role == ROLE_OUTPUT) {
+            AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type);
         }
-        if (role == ROLE_INPUT && !AudioDeviceInfo.isValidAudioDeviceTypeIn(type)) {
-            throw new IllegalArgumentException("Invalid input device type " + type);
+        if (role == ROLE_INPUT) {
+            AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type);
         }
 
         mRole = role;
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index db2a1e8..6b0e17d 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -280,6 +280,28 @@
         }
     }
 
+    /**
+     * @hide
+     * Throws IAE on an invalid output device type
+     * @param type
+     */
+    public static void enforceValidAudioDeviceTypeOut(int type) {
+        if (!isValidAudioDeviceTypeOut(type)) {
+            throw new IllegalArgumentException("Illegal output device type " + type);
+        }
+    }
+
+    /**
+     * @hide
+     * Throws IAE on an invalid input device type
+     * @param type
+     */
+    public static void enforceValidAudioDeviceTypeIn(int type) {
+        if (!isValidAudioDeviceTypeIn(type)) {
+            throw new IllegalArgumentException("Illegal input device type " + type);
+        }
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7408987e..8477aa3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4571,6 +4571,150 @@
         }
     }
 
+    /**
+     * @hide
+     * Volume behavior for an audio device where a software attenuation is applied
+     * @see #setDeviceVolumeBehavior(int, String, int)
+     */
+    public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0;
+    /**
+     * @hide
+     * Volume behavior for an audio device where the volume is always set to provide no attenuation
+     *     nor gain (e.g. unit gain).
+     * @see #setDeviceVolumeBehavior(int, String, int)
+     */
+    public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1;
+    /**
+     * @hide
+     * Volume behavior for an audio device where the volume is either set to muted, or to provide
+     *     no attenuation nor gain (e.g. unit gain).
+     * @see #setDeviceVolumeBehavior(int, String, int)
+     */
+    public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2;
+    /**
+     * @hide
+     * Volume behavior for an audio device where no software attenuation is applied, and
+     *     the volume is kept synchronized between the host and the device itself through a
+     *     device-specific protocol such as BT AVRCP.
+     * @see #setDeviceVolumeBehavior(int, String, int)
+     */
+    public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
+    /**
+     * @hide
+     * Volume behavior for an audio device where no software attenuation is applied, and
+     *     the volume is kept synchronized between the host and the device itself through a
+     *     device-specific protocol (such as for hearing aids), based on the audio mode (e.g.
+     *     normal vs in phone call).
+     * @see #setMode(int)
+     * @see #setDeviceVolumeBehavior(int, String, int)
+     */
+    public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
+
+    /** @hide */
+    @IntDef({
+            DEVICE_VOLUME_BEHAVIOR_VARIABLE,
+            DEVICE_VOLUME_BEHAVIOR_FULL,
+            DEVICE_VOLUME_BEHAVIOR_FIXED,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceVolumeBehavior {}
+
+    /**
+     * @hide
+     * Throws IAE on an invalid volume behavior value
+     * @param volumeBehavior behavior value to check
+     */
+    public static void enforceValidVolumeBehavior(int volumeBehavior) {
+        switch (volumeBehavior) {
+            case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
+            case DEVICE_VOLUME_BEHAVIOR_FULL:
+            case DEVICE_VOLUME_BEHAVIOR_FIXED:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+                return;
+            default:
+                throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
+        }
+    }
+
+    /**
+     * @hide
+     * Sets the volume behavior for an audio output device.
+     * @param deviceType the type of audio device to be affected. Currently only supports
+     *     {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC},
+     *     {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE}
+     * @param deviceAddress the address of the device, if any
+     * @param deviceVolumeBehavior one of the device behaviors
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setDeviceVolumeBehavior(int deviceType, @Nullable String deviceAddress,
+            @DeviceVolumeBehavior int deviceVolumeBehavior) {
+        setDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                deviceType, deviceAddress), deviceVolumeBehavior);
+    }
+
+    /**
+     * @hide
+     * Sets the volume behavior for an audio output device.
+     * @param device the device to be affected. Currently only supports devices of type
+     *     {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC},
+     *     {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE}
+     * @param deviceVolumeBehavior one of the device behaviors
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+            @DeviceVolumeBehavior int deviceVolumeBehavior) {
+        // verify arguments (validity of device type is enforced in server)
+        Objects.requireNonNull(device);
+        enforceValidVolumeBehavior(deviceVolumeBehavior);
+        // communicate with service
+        final IAudioService service = getService();
+        try {
+            service.setDeviceVolumeBehavior(device, deviceVolumeBehavior,
+                    mApplicationContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Returns the volume device behavior for the given device type and address
+     * @param deviceType an audio output device type, as defined in {@link AudioDeviceInfo}
+     * @param deviceAddress the address of the audio device, if any.
+     * @return the volume behavior for the device
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @DeviceVolumeBehavior int getDeviceVolumeBehavior(int deviceType,
+            @Nullable String deviceAddress) {
+        // verify arguments
+        AudioDeviceInfo.enforceValidAudioDeviceTypeOut(deviceType);
+        return getDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                deviceType, deviceAddress));
+    }
+
+    /**
+     * @hide
+     * Returns the volume device behavior for the given audio device
+     * @param device the audio device
+     * @return the volume behavior for the device
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device)
+    {
+        // verify arguments (validity of device type is enforced in server)
+        Objects.requireNonNull(device);
+        // communicate with service
+        final IAudioService service = getService();
+        try {
+            return service.getDeviceVolumeBehavior(device);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
      /**
      * Indicate wired accessory connection state change.
      * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 453a5d8..bb10e1f 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -294,6 +294,11 @@
 
     oneway void setRttEnabled(in boolean rttEnabled);
 
+    void setDeviceVolumeBehavior(in AudioDeviceAttributes device,
+             in int deviceVolumeBehavior, in String pkgName);
+
+    int getDeviceVolumeBehavior(in AudioDeviceAttributes device);
+
     // WARNING: read warning at top of file, new methods that need to be used by native
     // code via IAudioManager.h need to be added to the top section.
 }
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/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index d4494acb7e..cf1f1b5 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -602,6 +602,9 @@
      */
     @Nullable
     public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
+        if (mFrontend == null) {
+            throw new IllegalStateException("frontend is not initialized");
+        }
         return nativeGetFrontendStatus(statusTypes);
     }
 
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4a7e8e1..312e5fe 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -124,7 +124,11 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtSettings;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatus;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusType;
 using ::android::hardware::tv::tuner::V1_0::FrontendType;
 using ::android::hardware::tv::tuner::V1_0::ITuner;
 using ::android::hardware::tv::tuner::V1_0::LnbPosition;
@@ -1453,6 +1457,254 @@
             numBytesInSectionFilter, filterCaps, linkCaps, bTimeFilter);
 }
 
+jobject JTuner::getFrontendStatus(jintArray types) {
+    if (mFe == NULL) {
+        return NULL;
+    }
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jsize size = env->GetArrayLength(types);
+    std::vector<FrontendStatusType> v(size);
+    env->GetIntArrayRegion(types, 0, size, reinterpret_cast<jint*>(&v[0]));
+
+    Result res;
+    hidl_vec<FrontendStatus> status;
+    mFe->getStatus(v,
+            [&](Result r, const hidl_vec<FrontendStatus>& s) {
+                res = r;
+                status = s;
+            });
+    if (res != Result::SUCCESS) {
+        return NULL;
+    }
+
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatus");
+    jmethodID init = env->GetMethodID(clazz, "<init>", "()V");
+    jobject statusObj = env->NewObject(clazz, init);
+
+    jclass intClazz = env->FindClass("java/lang/Integer");
+    jmethodID initInt = env->GetMethodID(intClazz, "<init>", "(I)V");
+    jclass booleanClazz = env->FindClass("java/lang/Boolean");
+    jmethodID initBoolean = env->GetMethodID(booleanClazz, "<init>", "(Z)V");
+
+    for (auto s : status) {
+        switch(s.getDiscriminator()) {
+            case FrontendStatus::hidl_discriminator::isDemodLocked: {
+                jfieldID field = env->GetFieldID(clazz, "mIsDemodLocked", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isDemodLocked()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::snr: {
+                jfieldID field = env->GetFieldID(clazz, "mSnr", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.snr()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::ber: {
+                jfieldID field = env->GetFieldID(clazz, "mBer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.ber()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::per: {
+                jfieldID field = env->GetFieldID(clazz, "mPer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.per()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::preBer: {
+                jfieldID field = env->GetFieldID(clazz, "mPerBer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.preBer()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::signalQuality: {
+                jfieldID field = env->GetFieldID(clazz, "mSignalQuality", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.signalQuality()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::signalStrength: {
+                jfieldID field = env->GetFieldID(clazz, "mSignalStrength", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.signalStrength()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::symbolRate: {
+                jfieldID field = env->GetFieldID(clazz, "mSymbolRate", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.symbolRate()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::innerFec: {
+                jfieldID field = env->GetFieldID(clazz, "mInnerFec", "Ljava/lang/Long;");
+                jclass longClazz = env->FindClass("java/lang/Long");
+                jmethodID initLong = env->GetMethodID(longClazz, "<init>", "(J)V");
+                jobject newLongObj = env->NewObject(
+                        longClazz, initLong, static_cast<jlong>(s.innerFec()));
+                env->SetObjectField(statusObj, field, newLongObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::modulation: {
+                jfieldID field = env->GetFieldID(clazz, "mModulation", "Ljava/lang/Integer;");
+                FrontendModulationStatus modulation = s.modulation();
+                jint intModulation;
+                bool valid = true;
+                switch(modulation.getDiscriminator()) {
+                    case FrontendModulationStatus::hidl_discriminator::dvbc: {
+                        intModulation = static_cast<jint>(modulation.dvbc());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::dvbs: {
+                        intModulation = static_cast<jint>(modulation.dvbs());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::isdbs: {
+                        intModulation = static_cast<jint>(modulation.isdbs());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::isdbs3: {
+                        intModulation = static_cast<jint>(modulation.isdbs3());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::isdbt: {
+                        intModulation = static_cast<jint>(modulation.isdbt());
+                        break;
+                    }
+                    default: {
+                        valid = false;
+                        break;
+                    }
+                }
+                if (valid) {
+                    jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
+                    env->SetObjectField(statusObj, field, newIntegerObj);
+                }
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::inversion: {
+                jfieldID field = env->GetFieldID(clazz, "mInversion", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.inversion()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::lnbVoltage: {
+                jfieldID field = env->GetFieldID(clazz, "mLnbVoltage", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.lnbVoltage()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::plpId: {
+                jfieldID field = env->GetFieldID(clazz, "mPlpId", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.plpId()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isEWBS: {
+                jfieldID field = env->GetFieldID(clazz, "mIsEwbs", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isEWBS()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::agc: {
+                jfieldID field = env->GetFieldID(clazz, "mAgc", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.agc()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isLnaOn: {
+                jfieldID field = env->GetFieldID(clazz, "mIsLnaOn", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isLnaOn()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isLayerError: {
+                jfieldID field = env->GetFieldID(clazz, "mIsLayerErrors", "[Z");
+                hidl_vec<bool> layerErr = s.isLayerError();
+
+                jbooleanArray valObj = env->NewBooleanArray(layerErr.size());
+
+                for (size_t i = 0; i < layerErr.size(); i++) {
+                    jboolean x = layerErr[i];
+                    env->SetBooleanArrayRegion(valObj, i, 1, &x);
+                }
+                env->SetObjectField(statusObj, field, valObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::mer: {
+                jfieldID field = env->GetFieldID(clazz, "mMer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.mer()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::freqOffset: {
+                jfieldID field = env->GetFieldID(clazz, "mFreqOffset", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.freqOffset()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::hierarchy: {
+                jfieldID field = env->GetFieldID(clazz, "mHierarchy", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.hierarchy()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isRfLocked: {
+                jfieldID field = env->GetFieldID(clazz, "mIsRfLocked", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isRfLocked()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::plpInfo: {
+                jfieldID field = env->GetFieldID(clazz, "mPlpInfo",
+                        "[Landroid/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo;");
+                jclass plpClazz = env->FindClass(
+                        "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo");
+                jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZI)V");
+
+                hidl_vec<FrontendStatusAtsc3PlpInfo> plpInfos = s.plpInfo();
+
+                jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, NULL);
+                for (int i = 0; i < plpInfos.size(); i++) {
+                    auto info = plpInfos[i];
+                    jint plpId = (jint) info.plpId;
+                    jboolean isLocked = (jboolean) info.isLocked;
+                    jint uec = (jint) info.uec;
+
+                    jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
+                    env->SetObjectArrayElement(valObj, i, plpObj);
+                }
+
+                env->SetObjectField(statusObj, field, valObj);
+                break;
+            }
+            default: {
+                break;
+            }
+        }
+    }
+
+    return statusObj;
+}
+
 }  // namespace android
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -2086,8 +2338,10 @@
     return tuner->setLna(enable);
 }
 
-static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) {
-    return NULL;
+static jobject android_media_tv_Tuner_get_frontend_status(
+        JNIEnv* env, jobject thiz, jintArray types) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getFrontendStatus(types);
 }
 
 static jobject android_media_tv_Tuner_get_av_sync_hw_id(
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 7e860b9..e6f10b2 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -187,6 +187,7 @@
     jobject openDescrambler();
     jobject openDvr(DvrType type, jlong bufferSize);
     jobject getDemuxCaps();
+    jobject getFrontendStatus(jintArray types);
 
 protected:
     Result openDemux();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index f6679c0..9d98479 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -219,6 +219,9 @@
 
         mNavBarNotificationTouchListener =
                 (v, event) -> {
+                    if (!isInflated()) {
+                        return true;
+                    }
                     boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
                     if (consumed) {
                         return true;
diff --git a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
index 2e9381a..5f84587 100644
--- a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
+++ b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
@@ -14,9 +14,7 @@
   ~ limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.apk.cts.shim"
-          android:versionCode="2"
-          android:versionName="2.0" >
+          package="com.android.cts.ctsshim">
 
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P" />
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P.123" />
 </manifest>
\ No newline at end of file
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 5d363f3..68bd407 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -351,9 +351,6 @@
         @Override
         public void onProgress(float progress) {
             synchronized (mLock) {
-                if (progress == 0) {
-                    trackInfoWithIdLocked();
-                }
                 checkProgressUpdatedLocked(mInfo, (int) progress);
             }
         }
@@ -365,7 +362,6 @@
         @Override
         public void onError(@BugreportErrorCode int errorCode) {
             synchronized (mLock) {
-                trackInfoWithIdLocked();
                 stopProgressLocked(mInfo.id);
             }
             Log.e(TAG, "Bugreport API callback onError() errorCode = " + errorCode);
@@ -382,10 +378,10 @@
         }
 
         /**
-         * Reads bugreport id and links it to the bugreport info to track the bugreport's
-         * progress/completion/error. id is incremented in dumpstate code. This function is called
-         * when dumpstate calls one of the callback functions (onProgress, onFinished, onError)
-         * after the id has been incremented.
+         * Reads bugreport id and links it to the bugreport info to track a bugreport that is in
+         * process. id is incremented in the dumpstate code.
+         * We do not track a bugreport if there is already a bugreport with the same id being
+         * tracked.
          */
         @GuardedBy("mLock")
         private void trackInfoWithIdLocked() {
@@ -408,7 +404,6 @@
                 sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
                         mInfo.bugreportFile);
             } else {
-                trackInfoWithIdLocked();
                 cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
                 final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
                 intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
@@ -638,8 +633,11 @@
 
         BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(info);
         try {
-            mBugreportManager.startBugreport(bugreportFd, screenshotFd,
-                    new BugreportParams(bugreportType), executor, bugreportCallback);
+            synchronized (mLock) {
+                mBugreportManager.startBugreport(bugreportFd, screenshotFd,
+                        new BugreportParams(bugreportType), executor, bugreportCallback);
+                bugreportCallback.trackInfoWithIdLocked();
+            }
         } catch (RuntimeException e) {
             Log.i(TAG, "Error in generating bugreports: ", e);
             // The binder call didn't go through successfully, so need to close the fds.
@@ -756,7 +754,7 @@
                 != (info.lastProgress.intValue() / LOG_PROGRESS_STEP))) {
             Log.d(TAG, "Progress #" + info.id + ": " + percentageText);
         }
-        info.lastProgress = new AtomicInteger(progress);
+        info.lastProgress.set(progress);
 
         sendForegroundabledNotification(info.id, builder.build());
     }
@@ -1025,7 +1023,7 @@
         }
         Log.d(TAG, "Bugreport finished with title: " + info.getTitle()
                 + " and shareDescription: " + info.shareDescription);
-        info.finished = new AtomicBoolean(true);
+        info.finished.set(true);
 
         synchronized (mLock) {
             // Stop running on foreground, otherwise share notification cannot be dismissed.
@@ -1809,18 +1807,18 @@
          * Current value of progress (in percentage) of the bugreport generation as
          * displayed by the UI.
          */
-        AtomicInteger progress = new AtomicInteger(0);
+        final AtomicInteger progress = new AtomicInteger(0);
 
         /**
          * Last value of progress (in percentage) of the bugreport generation for which
          * system notification was updated.
          */
-        AtomicInteger lastProgress = new AtomicInteger(0);
+        final AtomicInteger lastProgress = new AtomicInteger(0);
 
         /**
          * Time of the last progress update.
          */
-        AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis());
+        final AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis());
 
         /**
          * Time of the last progress update when Parcel was created.
@@ -1840,7 +1838,7 @@
         /**
          * Whether dumpstate sent an intent informing it has finished.
          */
-        AtomicBoolean finished = new AtomicBoolean(false);
+        final AtomicBoolean finished = new AtomicBoolean(false);
 
         /**
          * Whether the details entries have been added to the bugreport yet.
@@ -2075,8 +2073,8 @@
             initialName = in.readString();
             title = in.readString();
             description = in.readString();
-            progress = new AtomicInteger(in.readInt());
-            lastUpdate = new AtomicLong(in.readLong());
+            progress.set(in.readInt());
+            lastUpdate.set(in.readLong());
             formattedLastUpdate = in.readString();
             bugreportFile = readFile(in);
 
@@ -2085,7 +2083,7 @@
                   screenshotFiles.add(readFile(in));
             }
 
-            finished = new AtomicBoolean(in.readInt() == 1);
+            finished.set(in.readInt() == 1);
             screenshotCounter = in.readInt();
             shareDescription = in.readString();
             shareTitle = in.readString();
@@ -2157,8 +2155,8 @@
                         + ") from " + info.progress.intValue() + " to " + progress);
             }
         }
-        info.progress = new AtomicInteger(progress);
-        info.lastUpdate = new AtomicLong(System.currentTimeMillis());
+        info.progress.set(progress);
+        info.lastUpdate.set(System.currentTimeMillis());
 
         updateProgress(info);
     }
diff --git a/packages/SystemUI/res/drawable/control_background_ripple.xml b/packages/SystemUI/res/drawable/control_background_ripple.xml
index 37914e2..27e3da9 100644
--- a/packages/SystemUI/res/drawable/control_background_ripple.xml
+++ b/packages/SystemUI/res/drawable/control_background_ripple.xml
@@ -17,7 +17,10 @@
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
         android:color="?android:attr/colorControlHighlight">
     <item android:id="@android:id/mask">
-        <color android:color="@android:color/white" />
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white" />
+            <corners android:radius="@dimen/control_corner_radius" />
+        </shape>
     </item>
     <item android:drawable="@drawable/control_background" />
 </ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
index 65b04fd..b3c7cf7 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">
 
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/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index b83e500..5119b59 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -109,6 +109,10 @@
         android:layout_height="wrap_content"
         android:layout_gravity="bottom|end"
         android:button="@drawable/controls_btn_star"
+        android:background="@android:color/transparent"
+        android:clickable="false"
+        android:selectable="false"
+        android:importantForAccessibility="no"
         android:layout_marginTop="4dp"
         android:layout_marginStart="4dp"
         app:layout_constraintStart_toEndOf="@id/subtitle"
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/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 23be78b..9074dad 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -477,6 +477,7 @@
     <dimen name="qs_tile_height">106dp</dimen>
     <dimen name="qs_tile_layout_margin_side">6dp</dimen>
     <dimen name="qs_tile_margin_horizontal">18dp</dimen>
+    <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
     <dimen name="qs_tile_margin_vertical">24dp</dimen>
     <dimen name="qs_tile_margin_top_bottom">12dp</dimen>
     <dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
@@ -1147,11 +1148,13 @@
     <!-- 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 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 29100ef..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..6742a4d 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/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7cbc840..57e3f14 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -971,13 +971,16 @@
         boolean changed = false;
 
         if (enabled && (oldIntent == null)) {
-            ComponentName poComponent = mDevicePolicyManager.getProfileOwnerAsUser(userId);
-            if (poComponent == null) {
-                Log.e(TAG, "No profile owner found for User " + userId);
+            ComponentName supervisorComponent =
+                    mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+                            UserHandle.of(userId));
+            if (supervisorComponent == null) {
+                Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User "
+                        + userId);
             } else {
                 Intent intent =
                         new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
-                                .setPackage(poComponent.getPackageName());
+                                .setPackage(supervisorComponent.getPackageName());
                 ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0);
                 if (resolveInfo != null && resolveInfo.serviceInfo != null) {
                     Intent launchIntent =
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index fc29f5c..2f10394 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -57,27 +57,27 @@
     /**
      * Called when the device started going to sleep.
      */
-    void onStartedGoingToSleep();
+    default void onStartedGoingToSleep() {};
 
     /**
      * Called when the device has finished going to sleep.
      */
-    void onFinishedGoingToSleep();
+    default void onFinishedGoingToSleep() {};
 
     /**
      * Called when the device started waking up.
      */
-    void onStartedWakingUp();
+    default void onStartedWakingUp() {};
 
     /**
      * Called when the device started turning on.
      */
-    void onScreenTurningOn();
+    default void onScreenTurningOn() {};
 
     /**
      * Called when the device has finished turning on.
      */
-    void onScreenTurnedOn();
+    default void onScreenTurnedOn() {};
 
     /**
      * Sets whether the Keyguard needs input.
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index e89c66e..74b94e7 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -166,7 +166,7 @@
 
     @VisibleForTesting
     protected open fun createUBRForUser(userId: Int) =
-            UserBroadcastDispatcher(context, userId, mainHandler, bgLooper)
+            UserBroadcastDispatcher(context, userId, bgLooper)
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("Broadcast dispatcher:")
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 0c631aa..4e84f06 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -27,7 +27,6 @@
 import android.util.ArrayMap
 import android.util.ArraySet
 import android.util.Log
-import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
 import com.android.internal.util.Preconditions
 import com.android.systemui.Dumpable
@@ -46,11 +45,13 @@
  *
  * Created by [BroadcastDispatcher] as needed by users. The value of [userId] can be
  * [UserHandle.USER_ALL].
+ *
+ * Each instance of this class will register itself exactly once with [Context]. Updates to the
+ * [IntentFilter] will be done in the background thread.
  */
 class UserBroadcastDispatcher(
     private val context: Context,
     private val userId: Int,
-    private val mainHandler: Handler,
     private val bgLooper: Looper
 ) : BroadcastReceiver(), Dumpable {
 
@@ -168,7 +169,7 @@
     // Only call this from a BG thread
     private fun createFilterAndRegisterReceiverBG() {
         val intentFilter = createFilter()
-        mainHandler.post(RegisterReceiverRunnable(intentFilter))
+        bgHandler.post(RegisterReceiverRunnable(intentFilter))
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
@@ -207,10 +208,7 @@
 
         /*
          * Registers and unregisters the BroadcastReceiver
-         *
-         * Must be called from Main Thread
          */
-        @MainThread
         override fun run() {
             if (registered.get()) {
                 context.unregisterReceiver(this@UserBroadcastDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 1c69594..be9cd5f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -549,6 +549,7 @@
                 Log.e(TAG, "Attempt to expand stack without selected bubble!");
                 return;
             }
+            mSelectedBubble.markUpdatedAt(mTimeSource.currentTimeMillis());
             mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
             mStateChange.orderChanged |= repackAll();
         } else if (!mBubbles.isEmpty()) {
@@ -662,7 +663,7 @@
 
     /**
      * This applies a full sort and group pass to all existing bubbles. The bubbles are grouped
-     * by groupId. Each group is then sorted by the max(lastUpdated) time of it's bubbles. Bubbles
+     * by groupId. Each group is then sorted by the max(lastUpdated) time of its bubbles. Bubbles
      * within each group are then sorted by lastUpdated descending.
      *
      * @return true if the position of any bubbles changed as a result
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 2231d11..3f985ef 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -81,11 +81,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);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 764fda0..1291dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -151,10 +151,10 @@
         subtitle.text = data.control.subtitle
         favorite.isChecked = data.favorite
         removed.text = if (data.removed) "Removed" else ""
-        favorite.setOnClickListener {
+        itemView.setOnClickListener {
+            favorite.isChecked = !favorite.isChecked
             favoriteCallback(data.control.controlId, favorite.isChecked)
         }
-        itemView.setOnClickListener { favorite.performClick() }
         applyRenderInfo(renderInfo)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 5911805..a7c4043 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -37,6 +37,8 @@
 import android.hardware.SensorPrivacyManager;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiManager;
 import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -193,6 +195,12 @@
         return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
     }
 
+    @Provides
+    @Singleton
+    static NetworkScoreManager provideNetworkScoreManager(Context context) {
+        return context.getSystemService(NetworkScoreManager.class);
+    }
+
     @Singleton
     @Provides
     static NotificationManager provideNotificationManager(Context context) {
@@ -273,6 +281,12 @@
         return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
     }
 
+    @Provides
+    @Singleton
+    static WifiManager provideWifiManager(Context context) {
+        return context.getSystemService(WifiManager.class);
+    }
+
     @Singleton
     @Provides
     static WindowManager provideWindowManager(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 3bed338..f7f9afd 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.doze;
 
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
 import android.annotation.MainThread;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Trace;
@@ -368,8 +371,8 @@
             case DOZE_PULSE_DONE:
                 final State nextState;
                 @Wakefulness int wakefulness = mWakefulnessLifecycle.getWakefulness();
-                if (wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
-                        || wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING) {
+                if (state != State.INITIALIZED && (wakefulness == WAKEFULNESS_AWAKE
+                        || wakefulness == WAKEFULNESS_WAKING)) {
                     nextState = State.FINISH;
                 } else if (mDockManager.isDocked()) {
                     nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
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/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 7974281..8397c65 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -162,7 +162,7 @@
     }
 
     /**
-     * Synchronizes the current bounds with the pinned stack.
+     * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
      */
     void synchronizePinnedStackBounds() {
         cancelAnimations();
@@ -178,6 +178,21 @@
     }
 
     /**
+     * Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This
+     * is done to prepare for a touch gesture.
+     */
+    void synchronizePinnedStackBoundsForTouchGesture() {
+        if (mAnimatingToBounds.isEmpty()) {
+            // If we're not animating anywhere, sync normally.
+            synchronizePinnedStackBounds();
+        } else {
+            // If we're animating, set the current bounds to the animated bounds. That way, the
+            // touch gesture will begin at the most recent animated location of the bounds.
+            mBounds.set(mAnimatedBounds);
+        }
+    }
+
+    /**
      * Tries to move the pinned stack to the given {@param bounds}.
      */
     void movePip(Rect toBounds) {
@@ -295,13 +310,7 @@
         final float estimatedFlingYEndValue =
                 PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
 
-        setAnimatingToBounds(new Rect(
-                (int) xEndValue,
-                (int) estimatedFlingYEndValue,
-                (int) xEndValue + mBounds.width(),
-                (int) estimatedFlingYEndValue + mBounds.height()));
-
-        startBoundsAnimation();
+        startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */);
     }
 
     /**
@@ -322,9 +331,7 @@
         mAnimatedBoundsPhysicsAnimator
                 .spring(FloatProperties.RECT_X, bounds.left, springConfig)
                 .spring(FloatProperties.RECT_Y, bounds.top, springConfig);
-        startBoundsAnimation();
-
-        setAnimatingToBounds(bounds);
+        startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */);
     }
 
     /**
@@ -349,7 +356,7 @@
                     (target, values) -> updateAction.run());
         }
 
-        startBoundsAnimation();
+        startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */);
     }
 
     /**
@@ -418,11 +425,23 @@
      * This will also add end actions to the bounds animator that cancel the TimeAnimator and update
      * the 'real' bounds to equal the final animated bounds.
      */
-    private void startBoundsAnimation() {
+    private void startBoundsAnimator(float toX, float toY) {
         cancelAnimations();
 
+        // Set animatingToBounds directly to avoid allocating a new Rect, but then call
+        // setAnimatingToBounds to run the normal logic for changing animatingToBounds.
+        mAnimatingToBounds.set(
+                (int) toX,
+                (int) toY,
+                (int) toX + mBounds.width(),
+                (int) toY + mBounds.height());
+        setAnimatingToBounds(mAnimatingToBounds);
+
         mAnimatedBoundsPhysicsAnimator
-                .withEndActions(() ->  mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds))
+                .withEndActions(() -> {
+                    mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds);
+                    mAnimatingToBounds.setEmpty();
+                })
                 .addUpdateListener(mResizePipUpdateListener)
                 .start();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index dfd5d2f..7cc2759 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -384,7 +384,7 @@
 
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
-                mMotionHelper.synchronizePinnedStackBounds();
+                mMotionHelper.synchronizePinnedStackBoundsForTouchGesture();
                 mGesture.onDown(mTouchState);
                 break;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index f710f7f..448531a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -25,13 +25,18 @@
 
 class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTileLayout {
 
+    companion object {
+        private const val NUM_LINES = 2
+    }
+
     protected val mRecords = ArrayList<QSPanel.TileRecord>()
     private var _listening = false
     private var smallTileSize = 0
     private val twoLineHeight
-        get() = smallTileSize * 2 + cellMarginVertical
+        get() = smallTileSize * NUM_LINES + cellMarginVertical * (NUM_LINES - 1)
     private var cellMarginHorizontal = 0
     private var cellMarginVertical = 0
+    private var tilesToShow = 0
 
     init {
         isFocusableInTouchMode = true
@@ -68,7 +73,7 @@
     override fun updateResources(): Boolean {
         with(mContext.resources) {
             smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
-            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal_two_line)
             cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
         }
         requestLayout()
@@ -83,11 +88,12 @@
         }
     }
 
-    override fun getNumVisibleTiles() = mRecords.size
+    override fun getNumVisibleTiles() = tilesToShow
 
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         updateResources()
+        postInvalidate()
     }
 
     override fun onFinishInflate() {
@@ -95,39 +101,58 @@
     }
 
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-        var previousView: View = this
-        var tiles = 0
 
         mRecords.forEach {
-            val tileView = it.tileView
-            if (tileView.visibility != View.GONE) {
-                tileView.updateAccessibilityOrder(previousView)
-                previousView = tileView
-                tiles++
-                tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
-            }
+            it.tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
         }
 
         val height = twoLineHeight
-        val columns = tiles / 2
-        val width = paddingStart + paddingEnd +
-                columns * smallTileSize +
-                (columns - 1) * cellMarginHorizontal
-        setMeasuredDimension(width, height)
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height)
     }
 
-    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
-        val tiles = mRecords.filter { it.tileView.visibility != View.GONE }
-        tiles.forEachIndexed {
-            index, tile ->
-            val column = index % (tiles.size / 2)
-            val left = getLeftForColumn(column)
-            val top = if (index < tiles.size / 2) 0 else getTopBottomRow()
-            tile.tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+    private fun calculateMaxColumns(availableWidth: Int): Int {
+        if (smallTileSize + cellMarginHorizontal == 0) {
+            return 0
+        } else {
+            return (availableWidth - smallTileSize) / (smallTileSize + cellMarginHorizontal) + 1
         }
     }
 
-    private fun getLeftForColumn(column: Int) = column * (smallTileSize + cellMarginHorizontal)
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        val availableWidth = r - l - paddingLeft - paddingRight
+        val maxColumns = calculateMaxColumns(availableWidth)
+        val actualColumns = Math.min(maxColumns, mRecords.size / NUM_LINES)
+        if (actualColumns == 0) {
+            // No tileSize or horizontal margin
+            return
+        }
+        tilesToShow = actualColumns * NUM_LINES
+
+        val interTileSpace = if (actualColumns <= 2) {
+            // Extra "column" of padding to be distributed on each end
+            (availableWidth - actualColumns * smallTileSize) / actualColumns
+        } else {
+            (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1)
+        }
+
+        for (index in 0 until mRecords.size) {
+            val tileView = mRecords[index].tileView
+            if (index >= tilesToShow) {
+                tileView.visibility = View.GONE
+            } else {
+                tileView.visibility = View.VISIBLE
+                if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView)
+                val column = index % actualColumns
+                val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2)
+                val top = if (index < actualColumns) 0 else getTopBottomRow()
+                tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+            }
+        }
+    }
+
+    private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int {
+        return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace)
+    }
 
     private fun getTopBottomRow() = smallTileSize + cellMarginVertical
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index f9b1473..ebdcc00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -25,6 +25,7 @@
 import android.os.Message;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
@@ -260,7 +261,9 @@
                 mCarrierGroups[i].setVisibility(View.GONE);
             }
             mNoSimTextView.setText(info.carrierText);
-            mNoSimTextView.setVisibility(View.VISIBLE);
+            if (!TextUtils.isEmpty(info.carrierText)) {
+                mNoSimTextView.setVisibility(View.VISIBLE);
+            }
         }
         handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
     }
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/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index e21861a..3879c16 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1085,14 +1085,6 @@
         crop.offsetTo(-(otherTaskRect.left - otherRect.left),
                 -(otherTaskRect.top - otherRect.top));
         t.setWindowCrop(mTiles.mSecondarySurface, crop);
-        // Reposition home and recents surfaces or they would be positioned relatively to its
-        // parent (split-screen secondary task) position.
-        for (int i = mTiles.mHomeAndRecentsSurfaces.size() - 1; i >= 0; --i) {
-            t.setPosition(mTiles.mHomeAndRecentsSurfaces.get(i),
-                    mTiles.mHomeBounds.left - otherTaskRect.left,
-                    mTiles.mHomeBounds.top - otherTaskRect.top);
-            t.setWindowCrop(mTiles.mHomeAndRecentsSurfaces.get(i), null);
-        }
         final SurfaceControl dividerCtrl = getWindowSurfaceControl();
         if (dividerCtrl != null) {
             if (isHorizontalDivision()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index d7322a0..fd44f04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController
@@ -68,6 +69,7 @@
     private var notificationAnimator: Animator? = null
     private var updateScheduled: Boolean = false
     private var shadeExpansion = 0f
+    private var ignoreShadeBlurUntilHidden: Boolean = false
     @VisibleForTesting
     var shadeSpring = DepthAnimation()
     @VisibleForTesting
@@ -83,6 +85,26 @@
         }
 
     /**
+     * When launching an app from the shade, the animations progress should affect how blurry the
+     * shade is, overriding the expansion amount.
+     */
+    var notificationLaunchAnimationParams: ActivityLaunchAnimator.ExpandAnimationParameters? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                scheduleUpdate()
+                return
+            }
+
+            if (shadeSpring.radius == 0) {
+                return
+            }
+            ignoreShadeBlurUntilHidden = true
+            shadeSpring.animateTo(0)
+            shadeSpring.finishIfRunning()
+        }
+
+    /**
      * Blur radius of the wake-up animation on this frame.
      */
     private var wakeAndUnlockBlurRadius = 0
@@ -99,9 +121,19 @@
     val updateBlurCallback = Choreographer.FrameCallback {
         updateScheduled = false
 
-        var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius)
-        shadeRadius = (shadeRadius * (1f - brightnessMirrorSpring.ratio)).toInt()
-        val blur = max(shadeRadius, globalActionsSpring.radius)
+        var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat()
+        shadeRadius *= 1f - brightnessMirrorSpring.ratio
+        val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f
+        shadeRadius *= (1f - launchProgress) * (1f - launchProgress)
+
+        if (ignoreShadeBlurUntilHidden) {
+            if (shadeRadius == 0f) {
+                ignoreShadeBlurUntilHidden = false
+            } else {
+                shadeRadius = 0f
+            }
+        }
+        val blur = max(shadeRadius.toInt(), globalActionsSpring.radius)
         blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
         try {
             wallpaperManager.setWallpaperZoomOut(root.windowToken,
@@ -187,7 +219,6 @@
         if (statusBarStateController.state == StatusBarState.SHADE) {
             newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
         }
-
         shadeSpring.animateTo(newBlur)
     }
 
@@ -212,6 +243,9 @@
             it.println("globalActionsRadius: ${globalActionsSpring.radius}")
             it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
             it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
+            it.println("notificationLaunchAnimationProgress: " +
+                    "${notificationLaunchAnimationParams?.linearProgress}")
+            it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden")
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 7c06157..6aef6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -57,6 +58,7 @@
     private final NotificationListContainer mNotificationContainer;
     private final float mWindowCornerRadius;
     private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private final NotificationShadeDepthController mDepthController;
     private Callback mCallback;
     private final Runnable mTimeoutRunnable = () -> {
         setAnimationPending(false);
@@ -70,9 +72,11 @@
             NotificationShadeWindowViewController notificationShadeWindowViewController,
             Callback callback,
             NotificationPanelViewController notificationPanel,
+            NotificationShadeDepthController depthController,
             NotificationListContainer container) {
         mNotificationPanel = notificationPanel;
         mNotificationContainer = container;
+        mDepthController = depthController;
         mNotificationShadeWindowViewController = notificationShadeWindowViewController;
         mCallback = callback;
         mWindowCornerRadius = ScreenDecorationsUtils
@@ -212,7 +216,7 @@
                                 mWindowCornerRadius, progress);
                         applyParamsToWindow(primary);
                         applyParamsToNotification(mParams);
-                        applyParamsToNotificationList(mParams);
+                        applyParamsToNotificationShade(mParams);
                     }
                 });
                 anim.addListener(new AnimatorListenerAdapter() {
@@ -256,14 +260,15 @@
             if (!running) {
                 mCallback.onExpandAnimationFinished(mIsFullScreenLaunch);
                 applyParamsToNotification(null);
-                applyParamsToNotificationList(null);
+                applyParamsToNotificationShade(null);
             }
 
         }
 
-        private void applyParamsToNotificationList(ExpandAnimationParameters params) {
+        private void applyParamsToNotificationShade(ExpandAnimationParameters params) {
             mNotificationContainer.applyExpandAnimationParams(params);
             mNotificationPanel.applyExpandAnimationParams(params);
+            mDepthController.setNotificationLaunchAnimationParams(params);
         }
 
         private void applyParamsToNotification(ExpandAnimationParameters params) {
@@ -295,7 +300,7 @@
     };
 
     public static class ExpandAnimationParameters {
-        float linearProgress;
+        public float linearProgress;
         int[] startPosition;
         float startTranslationZ;
         int left;
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..77376e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -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/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
new file mode 100644
index 0000000..57f8a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -0,0 +1,81 @@
+/*
+ * 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
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
+
+/**
+ * Stores the state that [ShadeListBuilder] assigns to this [ListEntry]
+ */
+data class ListAttachState private constructor(
+    /**
+     * Null if not attached to the current shade list. If top-level, then the shade list root. If
+     * part of a group, then that group's GroupEntry.
+     */
+    var parent: GroupEntry?,
+
+    /**
+     * The section that this ListEntry was sorted into. If the child of the group, this will be the
+     * parent's section. Null if not attached to the list.
+     */
+    var section: NotifSection?,
+    var sectionIndex: Int,
+
+    /**
+     * If a [NotifFilter] is excluding this entry from the list, then that filter. Always null for
+     * [GroupEntry]s.
+     */
+    var excludingFilter: NotifFilter?,
+
+    /**
+     * The [NotifPromoter] promoting this entry to top-level, if any. Always null for [GroupEntry]s.
+     */
+    var promoter: NotifPromoter?
+) {
+
+    /** Copies the state of another instance. */
+    fun clone(other: ListAttachState) {
+        parent = other.parent
+        section = other.section
+        sectionIndex = other.sectionIndex
+        excludingFilter = other.excludingFilter
+        promoter = other.promoter
+    }
+
+    /** Resets back to a "clean" state (the same as created by the factory method) */
+    fun reset() {
+        parent = null
+        section = null
+        sectionIndex = -1
+        excludingFilter = null
+        promoter = null
+    }
+
+    companion object {
+        @JvmStatic
+        fun create(): ListAttachState {
+            return ListAttachState(
+                    null,
+                    null,
+                    -1,
+                    null,
+                    null)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index b5c81b2..0caf0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -102,11 +102,11 @@
                     .append(")");
         }
 
-        if (entry.mNotifSection != null) {
+        if (entry.getNotifSection() != null) {
             sb.append(" sectionIndex=")
                     .append(entry.getSection())
                     .append(" sectionName=")
-                    .append(entry.mNotifSection.getName());
+                    .append(entry.getNotifSection().getName());
         }
 
         if (includeRecordKeeping) {
@@ -133,15 +133,15 @@
                         .append(" ");
             }
 
-            if (notifEntry.mExcludingFilter != null) {
+            if (notifEntry.getExcludingFilter() != null) {
                 rksb.append("filter=")
-                        .append(notifEntry.mExcludingFilter)
+                        .append(notifEntry.getExcludingFilter().getName())
                         .append(" ");
             }
 
-            if (notifEntry.mNotifPromoter != null) {
+            if (notifEntry.getNotifPromoter() != null) {
                 rksb.append("promoter=")
-                        .append(notifEntry.mNotifPromoter)
+                        .append(notifEntry.getNotifPromoter().getName())
                         .append(" ");
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index b048d03..837374f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
-import android.annotation.Nullable;
+
+import androidx.annotation.Nullable;
 
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 
@@ -27,13 +28,11 @@
 public abstract class ListEntry {
     private final String mKey;
 
-    @Nullable private GroupEntry mParent;
-    @Nullable private GroupEntry mPreviousParent;
-    @Nullable NotifSection mNotifSection;
-
-    private int mSection = -1;
     int mFirstAddedIteration = -1;
 
+    private final ListAttachState mPreviousAttachState = ListAttachState.create();
+    private final ListAttachState mAttachState = ListAttachState.create();
+
     ListEntry(String key) {
         mKey = key;
     }
@@ -51,27 +50,40 @@
     public abstract @Nullable NotificationEntry getRepresentativeEntry();
 
     @Nullable public GroupEntry getParent() {
-        return mParent;
+        return mAttachState.getParent();
     }
 
     void setParent(@Nullable GroupEntry parent) {
-        mParent = parent;
+        mAttachState.setParent(parent);
     }
 
     @Nullable public GroupEntry getPreviousParent() {
-        return mPreviousParent;
-    }
-
-    void setPreviousParent(@Nullable GroupEntry previousParent) {
-        mPreviousParent = previousParent;
+        return mPreviousAttachState.getParent();
     }
 
     /** The section this notification was assigned to (0 to N-1, where N is number of sections). */
     public int getSection() {
-        return mSection;
+        return mAttachState.getSectionIndex();
     }
 
-    void setSection(int section) {
-        mSection = section;
+    @Nullable public NotifSection getNotifSection() {
+        return mAttachState.getSection();
+    }
+
+    ListAttachState getAttachState() {
+        return mAttachState;
+    }
+
+    ListAttachState getPreviousAttachState() {
+        return mPreviousAttachState;
+    }
+
+    /**
+     * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
+     * fresh attach state (all entries will be null/default-initialized).
+     */
+    void beginNewAttachState() {
+        mPreviousAttachState.clone(mAttachState);
+        mAttachState.reset();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index c9cc670..9c2cac7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -504,6 +504,11 @@
                             extender));
         }
 
+        mLogger.logLifetimeExtensionEnded(
+                entry.getKey(),
+                extender,
+                entry.mLifetimeExtenders.size());
+
         if (!isLifetimeExtended(entry)) {
             if (tryRemoveNotification(entry)) {
                 dispatchEventsAndRebuildList();
@@ -529,6 +534,7 @@
         mAmDispatchingToOtherCode = true;
         for (NotifLifetimeExtender extender : mLifetimeExtenders) {
             if (extender.shouldExtendLifetime(entry, entry.mCancellationReason)) {
+                mLogger.logLifetimeExtended(entry.getKey(), extender);
                 entry.mLifetimeExtenders.add(extender);
             }
         }
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 808e1b3..d22564b 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;
@@ -105,18 +104,6 @@
     /** List of dismiss interceptors that are intercepting the dismissal of this notification. */
     final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
 
-    /** If this notification was filtered out, then the filter that did the filtering. */
-    @Nullable NotifFilter mExcludingFilter;
-
-    /**
-     * The NotifFilter, if any, that was active on this notification during the previous run of
-     * the list builder.
-     */
-    @Nullable NotifFilter mPreviousExcludingFilter;
-
-    /** If this was a group child that was promoted to the top level, then who did the promoting. */
-    @Nullable NotifPromoter mNotifPromoter;
-
     /**
      * If this notification was cancelled by system server, then the reason that was supplied.
      * Uncancelled notifications always have REASON_NOT_CANCELED. Note that lifetime-extended
@@ -283,6 +270,14 @@
         mDismissState = requireNonNull(dismissState);
     }
 
+    @Nullable public NotifFilter getExcludingFilter() {
+        return getAttachState().getExcludingFilter();
+    }
+
+    @Nullable public NotifPromoter getNotifPromoter() {
+        return getAttachState().getPromoter();
+    }
+
     /*
      * Convenience getters for SBN and Ranking members
      */
@@ -583,10 +578,6 @@
         if (row != null) row.resetUserExpansion();
     }
 
-    public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
-        if (row != null) row.freeContentViewWhenSafe(inflationFlag);
-    }
-
     public boolean rowExists() {
         return row != null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 19f7cef..0a3b02c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -58,6 +58,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -273,8 +274,6 @@
      * if we detect that behavior, we should crash instantly.
      */
     private void buildList() {
-        mLogger.logStartBuildList(mIterationCount);
-
         mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
         mPipelineState.setState(STATE_BUILD_STARTED);
 
@@ -316,21 +315,23 @@
 
         // Step 7: Lock in our group structure and log anything that's changed since the last run
         mPipelineState.incrementTo(STATE_FINALIZING);
-        logFilterChanges();
-        logParentingChanges();
+        logChanges();
         freeEmptyGroups();
 
         // Step 8: Dispatch the new list, first to any listeners and then to the view layer
-        if (mIterationCount % 10 == 0) {
-            mLogger.logFinalList(mNotifList);
-        }
         dispatchOnBeforeRenderList(mReadOnlyNotifList);
         if (mOnRenderListListener != null) {
             mOnRenderListListener.onRenderList(mReadOnlyNotifList);
         }
 
         // Step 9: We're done!
-        mLogger.logEndBuildList(mIterationCount);
+        mLogger.logEndBuildList(
+                mIterationCount,
+                mReadOnlyNotifList.size(),
+                countChildren(mReadOnlyNotifList));
+        if (mIterationCount % 10 == 0) {
+            mLogger.logFinalList(mNotifList);
+        }
         mPipelineState.setState(STATE_IDLE);
         mIterationCount++;
     }
@@ -354,18 +355,13 @@
 
     private void resetNotifs() {
         for (GroupEntry group : mGroups.values()) {
-            group.setPreviousParent(group.getParent());
-            group.setParent(null);
+            group.beginNewAttachState();
             group.clearChildren();
             group.setSummary(null);
         }
 
         for (NotificationEntry entry : mAllEntries) {
-            entry.setPreviousParent(entry.getParent());
-            entry.setParent(null);
-
-            entry.mPreviousExcludingFilter = entry.mExcludingFilter;
-            entry.mExcludingFilter = null;
+            entry.beginNewAttachState();
 
             if (entry.mFirstAddedIteration == -1) {
                 entry.mFirstAddedIteration = mIterationCount;
@@ -439,6 +435,7 @@
                         group.setSummary(entry);
                     } else {
                         mLogger.logDuplicateSummary(
+                                mIterationCount,
                                 group.getKey(),
                                 existingSummary.getKey(),
                                 entry.getKey());
@@ -460,7 +457,7 @@
 
                 final String topLevelKey = entry.getKey();
                 if (mGroups.containsKey(topLevelKey)) {
-                    mLogger.logDuplicateTopLevelKey(topLevelKey);
+                    mLogger.logDuplicateTopLevelKey(mIterationCount, topLevelKey);
                 } else {
                     entry.setParent(ROOT_ENTRY);
                     out.add(entry);
@@ -591,10 +588,10 @@
      * filtered out during any of the filtering steps.
      */
     private void annulAddition(ListEntry entry) {
-        // TODO: We should null out the entry's section and promoter here. However, if we do that,
-        //  future runs will think that the section changed. We need a mPreviousNotifSection,
-        //  similar to what we do for parents.
         entry.setParent(null);
+        entry.getAttachState().setSectionIndex(-1);
+        entry.getAttachState().setSection(null);
+        entry.getAttachState().setPromoter(null);
         if (entry.mFirstAddedIteration == mIterationCount) {
             entry.mFirstAddedIteration = -1;
         }
@@ -607,8 +604,8 @@
             if (entry instanceof GroupEntry) {
                 GroupEntry parent = (GroupEntry) entry;
                 for (NotificationEntry child : parent.getChildren()) {
-                    child.mNotifSection = sectionWithIndex.first;
-                    child.setSection(sectionWithIndex.second);
+                    child.getAttachState().setSection(sectionWithIndex.first);
+                    child.getAttachState().setSectionIndex(sectionWithIndex.second);
                 }
                 parent.sortChildren(sChildComparator);
             }
@@ -622,36 +619,52 @@
         mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty());
     }
 
-    private void logFilterChanges() {
+    private void logChanges() {
         for (NotificationEntry entry : mAllEntries) {
-            if (entry.mExcludingFilter != entry.mPreviousExcludingFilter) {
-                mLogger.logFilterChanged(
-                        entry.getKey(),
-                        entry.mPreviousExcludingFilter,
-                        entry.mExcludingFilter);
-            }
+            logAttachStateChanges(entry);
+        }
+        for (GroupEntry group : mGroups.values()) {
+            logAttachStateChanges(group);
         }
     }
 
-    private void logParentingChanges() {
-        for (NotificationEntry entry : mAllEntries) {
-            if (entry.getParent() != entry.getPreviousParent()) {
-                mLogger.logParentChanged(
-                        entry.getKey(),
-                        entry.getPreviousParent() == null
-                                ? null : entry.getPreviousParent().getKey(),
-                        entry.getParent() == null
-                                ? null : entry.getParent().getKey());
+    private void logAttachStateChanges(ListEntry entry) {
+
+        final ListAttachState curr = entry.getAttachState();
+        final ListAttachState prev = entry.getPreviousAttachState();
+
+        if (!Objects.equals(curr, prev)) {
+            mLogger.logEntryAttachStateChanged(
+                    mIterationCount,
+                    entry.getKey(),
+                    prev.getParent(),
+                    curr.getParent());
+
+            if (curr.getParent() != prev.getParent()) {
+                mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
             }
-        }
-        for (GroupEntry group : mGroups.values()) {
-            if (group.getParent() != group.getPreviousParent()) {
-                mLogger.logParentChanged(
-                        group.getKey(),
-                        group.getPreviousParent() == null
-                                ? null : group.getPreviousParent().getKey(),
-                        group.getParent() == null
-                                ? null : group.getParent().getKey());
+
+            if (curr.getExcludingFilter() != prev.getExcludingFilter()) {
+                mLogger.logFilterChanged(
+                        mIterationCount, prev.getExcludingFilter(), curr.getExcludingFilter());
+            }
+
+            // When something gets detached, its promoter and section are always set to null, so
+            // don't bother logging those changes.
+            final boolean wasDetached = curr.getParent() == null && prev.getParent() != null;
+
+            if (!wasDetached && curr.getPromoter() != prev.getPromoter()) {
+                mLogger.logPromoterChanged(
+                        mIterationCount, prev.getPromoter(), curr.getPromoter());
+            }
+
+            if (!wasDetached && curr.getSection() != prev.getSection()) {
+                mLogger.logSectionChanged(
+                        mIterationCount,
+                        prev.getSection(),
+                        prev.getSectionIndex(),
+                        curr.getSection(),
+                        curr.getSectionIndex());
             }
         }
     }
@@ -698,8 +711,9 @@
     };
 
     private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
-        entry.mExcludingFilter = findRejectingFilter(entry, now, filters);
-        return entry.mExcludingFilter != null;
+        final NotifFilter filter = findRejectingFilter(entry, now, filters);
+        entry.getAttachState().setExcludingFilter(filter);
+        return filter != null;
     }
 
     @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
@@ -717,15 +731,7 @@
 
     private boolean applyTopLevelPromoters(NotificationEntry entry) {
         NotifPromoter promoter = findPromoter(entry);
-
-        if (promoter != entry.mNotifPromoter) {
-            mLogger.logPromoterChanged(
-                    entry.getKey(),
-                    entry.mNotifPromoter != null ? entry.mNotifPromoter.getName() : null,
-                    promoter != null ? promoter.getName() : null);
-            entry.mNotifPromoter = promoter;
-        }
-
+        entry.getAttachState().setPromoter(promoter);
         return promoter != null;
     }
 
@@ -744,17 +750,8 @@
         final NotifSection section = sectionWithIndex.first;
         final Integer sectionIndex = sectionWithIndex.second;
 
-        if (section != entry.mNotifSection) {
-            mLogger.logSectionChanged(
-                    entry.getKey(),
-                    entry.mNotifSection != null ? entry.mNotifSection.getName() : null,
-                    entry.getSection(),
-                    section.getName(),
-                    sectionIndex);
-
-            entry.mNotifSection = section;
-            entry.setSection(sectionIndex);
-        }
+        entry.getAttachState().setSection(section);
+        entry.getAttachState().setSectionIndex(sectionIndex);
 
         return sectionWithIndex;
     }
@@ -776,6 +773,17 @@
         }
     }
 
+    private static int countChildren(List<ListEntry> entries) {
+        int count = 0;
+        for (int i = 0; i < entries.size(); i++) {
+            final ListEntry entry = entries.get(i);
+            if (entry instanceof GroupEntry) {
+                count += ((GroupEntry) entry).getChildren().size();
+            }
+        }
+        return count;
+    }
+
     private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
         for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
             mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
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/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/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index e946cf1..aa10782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
 import javax.inject.Inject
 
 class ShadeListBuilderLogger @Inject constructor(
@@ -36,19 +38,13 @@
         })
     }
 
-    fun logStartBuildList(iterationCount: Int) {
+    fun logEndBuildList(iterationCount: Int, topLevelEntries: Int, numChildren: Int) {
         buffer.log(TAG, INFO, {
-            int1 = iterationCount
+            long1 = iterationCount.toLong()
+            int1 = topLevelEntries
+            int2 = numChildren
         }, {
-            "Starting to build shade list (run #$int1)"
-        })
-    }
-
-    fun logEndBuildList(iterationCount: Int) {
-        buffer.log(TAG, INFO, {
-            int1 = iterationCount
-        }, {
-            "Finished building shade list (run #$int1)"
+            "(Build $long1) Build complete ($int1 top-level entries, $int2 children)"
         })
     }
 
@@ -97,90 +93,115 @@
         })
     }
 
-    fun logDuplicateSummary(groupKey: String, existingKey: String, newKey: String) {
+    fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) {
         buffer.log(TAG, WARNING, {
+            int1 = buildId
             str1 = groupKey
             str2 = existingKey
             str3 = newKey
         }, {
-            """Duplicate summary for group "$str1": "$str2" vs. "$str3""""
+            """(Build $int1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
         })
     }
 
-    fun logDuplicateTopLevelKey(topLevelKey: String) {
+    fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) {
         buffer.log(TAG, WARNING, {
+            int1 = buildId
             str1 = topLevelKey
         }, {
-            "Duplicate top-level key: $str1"
+            "(Build $int1) Duplicate top-level key: $str1"
         })
     }
 
-    fun logParentChanged(key: String, prevParent: String?, newParent: String?) {
+    fun logEntryAttachStateChanged(
+        buildId: Int,
+        key: String,
+        prevParent: GroupEntry?,
+        newParent: GroupEntry?
+    ) {
         buffer.log(TAG, INFO, {
+            int1 = buildId
             str1 = key
-            str2 = prevParent
-            str3 = newParent
+            str2 = prevParent?.key
+            str3 = newParent?.key
         }, {
-            "Parent change for $str1: $str2 -> $str3"
+            if (str2 == null && str3 != null) {
+                "(Build $int1) ATTACHED {$str1}"
+            } else if (str2 != null && str3 == null) {
+                "(Build $int1) DETACHED {$str1}"
+            } else {
+                "(Build $int1) MODIFIED {$str1}"
+            }
+        })
+    }
+
+    fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
+        buffer.log(TAG, INFO, {
+            int1 = buildId
+            str1 = prevParent?.key
+            str2 = newParent?.key
+        }, {
+            if (str1 == null && str2 != null) {
+                "(Build $int1)     Parent is {$str2}"
+            } else if (str1 != null && str2 == null) {
+                "(Build $int1)     Parent was {$str1}"
+            } else {
+                "(Build $int1)     Reparent: {$str2} -> {$str3}"
+            }
         })
     }
 
     fun logFilterChanged(
-        key: String,
+        buildId: Int,
         prevFilter: NotifFilter?,
         newFilter: NotifFilter?
     ) {
         buffer.log(TAG, INFO, {
-            str1 = key
-            str2 = prevFilter?.name
-            str3 = newFilter?.name
+            int1 = buildId
+            str1 = prevFilter?.name
+            str2 = newFilter?.name
         }, {
-            "Filter changed for $str1: $str2 -> $str3"
+            "(Build $int1)     Filter changed: $str1 -> $str2"
         })
     }
 
     fun logPromoterChanged(
-        key: String,
-        prevPromoter: String?,
-        newPromoter: String?
+        buildId: Int,
+        prevPromoter: NotifPromoter?,
+        newPromoter: NotifPromoter?
     ) {
         buffer.log(TAG, INFO, {
-            str1 = key
-            str2 = prevPromoter
-            str3 = newPromoter
+            int1 = buildId
+            str1 = prevPromoter?.name
+            str2 = newPromoter?.name
         }, {
-            "Promoter changed for $str1: $str2 -> $str3"
+            "(Build $int1)     Promoter changed: $str1 -> $str2"
         })
     }
 
     fun logSectionChanged(
-        key: String,
-        prevSection: String?,
+        buildId: Int,
+        prevSection: NotifSection?,
         prevIndex: Int,
-        section: String,
-        index: Int
+        newSection: NotifSection?,
+        newIndex: Int
     ) {
         buffer.log(TAG, INFO, {
-            str1 = key
-            str2 = section
-            int1 = index
-            str3 = prevSection
-            int2 = prevIndex
+            long1 = buildId.toLong()
+            str1 = prevSection?.name
+            int1 = prevIndex
+            str2 = newSection?.name
+            int2 = newIndex
         }, {
-            if (str3 == null) {
-                "Section assigned for $str1: '$str2' (#$int1)"
+            if (str1 == null) {
+                "(Build $long1)     Section assigned: '$str2' (#$int2)"
             } else {
-                "Section changed for $str1: '$str3' (#$int2) -> '$str2' (#$int1)"
+                "(Build $long1)     Section changed: '$str1' (#$int1) -> '$str2' (#$int2)"
             }
         })
     }
 
     fun logFinalList(entries: List<ListEntry>) {
-        buffer.log(TAG, DEBUG, {
-            int1 = entries.size
-        }, {
-            "List is finalized ($int1 top-level entries):"
-        })
         if (entries.isEmpty()) {
             buffer.log(TAG, DEBUG, {}, { "(empty list)" })
         }
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/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index ef302f6..c69882d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -111,6 +111,29 @@
             "RemoteException while attempting to clear all notifications:\n$str1"
         })
     }
+
+    fun logLifetimeExtended(key: String, extender: NotifLifetimeExtender) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+            str2 = extender.name
+        }, {
+            "LIFETIME EXTENDED: $str1 by $str2"
+        })
+    }
+
+    fun logLifetimeExtensionEnded(
+        key: String,
+        extender: NotifLifetimeExtender,
+        totalExtenders: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+            str2 = extender.name
+            int1 = totalExtenders
+        }, {
+            "LIFETIME EXTENSION ENDED for $str1 by '$str2'; $int1 remaining extensions"
+        })
+    }
 }
 
 private const val TAG = "NotifCollection"
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/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 19b5f5c..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;
@@ -89,6 +84,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -148,6 +144,7 @@
     private KeyguardBypassController mBypassController;
     private LayoutListener mLayoutListener;
     private RowContentBindStage mRowContentBindStage;
+    private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private int mIconTransformContentShift;
     private int mMaxHeadsUpHeightBeforeN;
     private int mMaxHeadsUpHeightBeforeP;
@@ -461,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 */);
     }
 
     /**
@@ -1145,7 +1117,7 @@
     @Override
     public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
         boolean existed = mMenuRow.getMenuView() != null;
-        mMenuRow = new NotificationMenuRow(mContext);
+        mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         if (existed) {
             createMenu();
         }
@@ -1569,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 */);
             }
@@ -1582,7 +1554,6 @@
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
         initDimens();
@@ -1603,9 +1574,13 @@
             NotificationMediaManager notificationMediaManager,
             OnAppOpsClickListener onAppOpsClickListener,
             FalsingManager falsingManager,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mAppName = appName;
-        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+        if (mMenuRow == null) {
+            mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);
+        }
+        if (mMenuRow.getMenuView() != null) {
             mMenuRow.setAppName(mAppName);
         }
         mLogger = logger;
@@ -1620,6 +1595,7 @@
         setAppOpsOnClickListener(onAppOpsClickListener);
         mFalsingManager = falsingManager;
         mStatusbarStateController = statusBarStateController;
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
     }
 
     private void initDimens() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 39fab43..8b3d06b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -28,6 +28,7 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
 import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
@@ -68,6 +69,7 @@
     private Runnable mOnDismissRunnable;
     private final FalsingManager mFalsingManager;
     private final boolean mAllowLongPress;
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     @Inject
     public ExpandableNotificationRowController(ExpandableNotificationRow view,
@@ -83,7 +85,8 @@
             NotificationRowContentBinder.InflationCallback inflationCallback,
             NotificationGutsManager notificationGutsManager,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
-            @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) {
+            @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager,
+            PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mView = view;
         mActivatableNotificationViewController = activatableNotificationViewController;
         mMediaManager = mediaManager;
@@ -104,6 +107,7 @@
         mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
         mAllowLongPress = allowLongPress;
         mFalsingManager = falsingManager;
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
     }
 
     /**
@@ -123,7 +127,8 @@
                 mMediaManager,
                 mOnAppOpsClickListener,
                 mFalsingManager,
-                mStatusBarStateController
+                mStatusBarStateController,
+                mPeopleNotificationIdentifier
         );
         mView.setOnDismissRunnable(mOnDismissRunnable);
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
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/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 212cba6..83a6eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -45,8 +45,9 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -114,12 +115,16 @@
 
     private boolean mIsUserTouching;
 
-    public NotificationMenuRow(Context context) {
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+    public NotificationMenuRow(Context context,
+            PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mContext = context;
         mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
         mHandler = new Handler(Looper.getMainLooper());
         mLeftMenuItems = new ArrayList<>();
         mRightMenuItems = new ArrayList<>();
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
     }
 
     @Override
@@ -260,7 +265,10 @@
             mSnoozeItem = createSnoozeItem(mContext);
         }
         mAppOpsItem = createAppOpsItem(mContext);
-        if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+        NotificationEntry entry = mParent.getEntry();
+        int personNotifType = mPeopleNotificationIdentifier
+                .getPeopleNotificationType(entry.getSbn(), entry.getRanking());
+        if (personNotifType != PeopleNotificationIdentifier.TYPE_NON_PERSON) {
             mInfoItem = createConversationItem(mContext);
         } else {
             mInfoItem = createInfoItem(mContext);
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..908d228 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;
@@ -4806,7 +4807,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/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b46ca40..8636cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -104,6 +104,8 @@
 
     private DisplayCutout mDisplayCutout;
     private int mRoundedCornerPadding = 0;
+    // right and left padding applied to this view to account for cutouts and rounded corners
+    private Pair<Integer, Integer> mPadding = new Pair(0, 0);
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -158,10 +160,13 @@
                 getResources().getDimensionPixelSize(
                         com.android.internal.R.dimen.text_size_small_material));
         lp = (MarginLayoutParams) mCarrierLabel.getLayoutParams();
-        lp.setMarginStart(
-                getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin));
-        mCarrierLabel.setLayoutParams(lp);
 
+        int marginStart = calculateMargin(
+                getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin),
+                mPadding.first);
+        lp.setMarginStart(marginStart);
+
+        mCarrierLabel.setLayoutParams(lp);
         updateKeyguardStatusBarHeight();
     }
 
@@ -220,6 +225,7 @@
                 : 0;
         int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
                 baseMarginEnd;
+        marginEnd = calculateMargin(marginEnd, mPadding.second);
         if (marginEnd != lp.getMarginEnd()) {
             lp.setMarginEnd(marginEnd);
             mSystemIconsContainer.setLayoutParams(lp);
@@ -252,10 +258,10 @@
     private void updatePadding(Pair<Integer, Integer> cornerCutoutMargins) {
         final int waterfallTop =
                 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
-        Pair<Integer, Integer> padding =
+        mPadding =
                 StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
                         mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
-        setPadding(padding.first, waterfallTop, padding.second, 0);
+        setPadding(mPadding.first, waterfallTop, mPadding.second, 0);
     }
 
     private boolean updateLayoutParamsNoCutout() {
@@ -502,6 +508,17 @@
         }
     }
 
+    /**
+     * Calculates the margin that isn't already accounted for in the view's padding.
+     */
+    private int calculateMargin(int margin, int padding) {
+        if (padding >= margin) {
+            return 0;
+        } else {
+            return margin - padding;
+        }
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusBarView:");
         pw.println("  mBatteryCharging: " + mBatteryCharging);
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/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ae2c78b..fa55b74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1235,6 +1235,7 @@
         // Set up the initial notification state.
         mActivityLaunchAnimator = new ActivityLaunchAnimator(
                 mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
+                mNotificationShadeDepthControllerLazy.get(),
                 (NotificationListContainer) mStackScroller);
 
         // TODO: inject this.
@@ -3338,12 +3339,12 @@
         Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
         Trace.beginSection("StatusBar#updateDozingState");
 
-        boolean sleepingFromKeyguard =
-                mStatusBarKeyguardViewManager.isGoingToSleepVisibleNotOccluded();
+        boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing()
+                && !mStatusBarKeyguardViewManager.isOccluded();
         boolean wakeAndUnlock = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
         boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
-                || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
+                || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && visibleNotOccluded);
 
         mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
         updateQsExpansionEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 31db8eb..45719c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -168,7 +168,6 @@
     private boolean mLastIsDocked;
     private boolean mLastPulsing;
     private int mLastBiometricMode;
-    private boolean mGoingToSleepVisibleNotOccluded;
     private boolean mLastLockVisible;
 
     private OnDismissAction mAfterKeyguardGoneAction;
@@ -450,37 +449,12 @@
         }
     }
 
-    public boolean isGoingToSleepVisibleNotOccluded() {
-        return mGoingToSleepVisibleNotOccluded;
-    }
-
-    @Override
-    public void onStartedGoingToSleep() {
-        mGoingToSleepVisibleNotOccluded = isShowing() && !isOccluded();
-    }
-
     @Override
     public void onFinishedGoingToSleep() {
-        mGoingToSleepVisibleNotOccluded = false;
         mBouncer.onScreenTurnedOff();
     }
 
     @Override
-    public void onStartedWakingUp() {
-        // TODO: remove
-    }
-
-    @Override
-    public void onScreenTurningOn() {
-        // TODO: remove
-    }
-
-    @Override
-    public void onScreenTurnedOn() {
-        // TODO: remove
-    }
-
-    @Override
     public void onRemoteInputActive(boolean active) {
         mRemoteInputActive = active;
         updateStates();
@@ -999,7 +973,6 @@
         pw.println("  mOccluded: " + mOccluded);
         pw.println("  mRemoteInputActive: " + mRemoteInputActive);
         pw.println("  mDozing: " + mDozing);
-        pw.println("  mGoingToSleepVisibleNotOccluded: " + mGoingToSleepVisibleNotOccluded);
         pw.println("  mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
         pw.println("  mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables);
         pw.println("  mPendingWakeupAction: " + mPendingWakeupAction);
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/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 99709402..6e5f8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -33,6 +33,7 @@
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -173,10 +174,13 @@
     @Inject
     public NetworkControllerImpl(Context context, @Background Looper bgLooper,
             DeviceProvisionedController deviceProvisionedController,
-            BroadcastDispatcher broadcastDispatcher) {
-        this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
-                (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
+            BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager,
+            TelephonyManager telephonyManager, WifiManager wifiManager,
+            NetworkScoreManager networkScoreManager) {
+        this(context, connectivityManager,
+                telephonyManager,
+                wifiManager,
+                networkScoreManager,
                 SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
                 new CallbackHandler(),
                 new AccessPointControllerImpl(context),
@@ -190,6 +194,7 @@
     @VisibleForTesting
     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
             TelephonyManager telephonyManager, WifiManager wifiManager,
+            NetworkScoreManager networkScoreManager,
             SubscriptionManager subManager, Config config, Looper bgLooper,
             CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
@@ -229,7 +234,7 @@
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
-                mCallbackHandler, this, mWifiManager);
+                mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager);
 
         mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
 
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/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index c2fc18f..b258fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -42,13 +42,10 @@
 
     public WifiSignalController(Context context, boolean hasMobileDataFeature,
             CallbackHandler callbackHandler, NetworkControllerImpl networkController,
-            WifiManager wifiManager) {
+            WifiManager wifiManager, ConnectivityManager connectivityManager,
+            NetworkScoreManager networkScoreManager) {
         super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
                 callbackHandler, networkController);
-        NetworkScoreManager networkScoreManager =
-                context.getSystemService(NetworkScoreManager.class);
-        ConnectivityManager connectivityManager =
-                context.getSystemService(ConnectivityManager.class);
         mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager,
                 connectivityManager, this::handleStatusUpdated);
         mWifiTracker.setListening(true);
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 0242e83..9ccb9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -21,15 +21,13 @@
 import android.app.INotificationManager;
 import android.app.ITransientNotificationCallback;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.widget.Toast;
 import android.widget.ToastPresenter;
 
 import com.android.internal.R;
@@ -49,18 +47,14 @@
 public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
     private static final String TAG = "ToastUI";
 
-    /**
-     * Values taken from {@link Toast}.
-     */
-    private static final long DURATION_SHORT = 4000;
-    private static final long DURATION_LONG = 7000;
-
     private final CommandQueue mCommandQueue;
     private final WindowManager mWindowManager;
     private final INotificationManager mNotificationManager;
     private final AccessibilityManager mAccessibilityManager;
-    private final ToastPresenter mPresenter;
-    private ToastEntry mCurrentToast;
+    private final int mGravity;
+    private final int mY;
+    @Nullable private ToastPresenter mPresenter;
+    @Nullable private ITransientNotificationCallback mCallback;
 
     @Inject
     public ToastUI(Context context, CommandQueue commandQueue) {
@@ -79,7 +73,9 @@
         mWindowManager = windowManager;
         mNotificationManager = notificationManager;
         mAccessibilityManager = accessibilityManager;
-        mPresenter = new ToastPresenter(context, accessibilityManager);
+        Resources resources = mContext.getResources();
+        mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
+        mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
     }
 
     @Override
@@ -91,33 +87,21 @@
     @MainThread
     public void showToast(String packageName, IBinder token, CharSequence text,
             IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
-        if (mCurrentToast != null) {
+        if (mPresenter != null) {
             hideCurrentToast();
         }
-        View view = mPresenter.getTextToastView(text);
-        LayoutParams params = getLayoutParams(packageName, windowToken, duration);
-        mCurrentToast = new ToastEntry(packageName, token, view, windowToken, callback);
-        try {
-            mWindowManager.addView(view, params);
-        } catch (WindowManager.BadTokenException e) {
-            Log.w(TAG, "Error while attempting to show toast from " + packageName, e);
-            return;
-        }
-        mPresenter.trySendAccessibilityEvent(view, packageName);
-        if (callback != null) {
-            try {
-                callback.onToastShown();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Error calling back " + packageName + " to notify onToastShow()", e);
-            }
-        }
+        View view = ToastPresenter.getTextToastView(mContext, text);
+        mCallback = callback;
+        mPresenter = new ToastPresenter(mContext, mWindowManager, mAccessibilityManager,
+                mNotificationManager, packageName);
+        mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
     }
 
     @Override
     @MainThread
     public void hideToast(String packageName, IBinder token) {
-        if (mCurrentToast == null || !Objects.equals(mCurrentToast.packageName, packageName)
-                || !Objects.equals(mCurrentToast.token, token)) {
+        if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName)
+                || !Objects.equals(mPresenter.getToken(), token)) {
             Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
             return;
         }
@@ -126,51 +110,7 @@
 
     @MainThread
     private void hideCurrentToast() {
-        if (mCurrentToast.view.getParent() != null) {
-            mWindowManager.removeViewImmediate(mCurrentToast.view);
-        }
-        String packageName = mCurrentToast.packageName;
-        try {
-            mNotificationManager.finishToken(packageName, mCurrentToast.windowToken);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error finishing toast window token from package " + packageName, e);
-        }
-        if (mCurrentToast.callback != null) {
-            try {
-                mCurrentToast.callback.onToastHidden();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Error calling back " + packageName + " to notify onToastHide()", e);
-            }
-        }
-        mCurrentToast = null;
-    }
-
-    private LayoutParams getLayoutParams(String packageName, IBinder windowToken, int duration) {
-        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-        mPresenter.startLayoutParams(params, packageName);
-        int gravity = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_toastDefaultGravity);
-        int yOffset = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
-        mPresenter.adjustLayoutParams(params, windowToken, duration, gravity, 0, yOffset, 0, 0);
-        return params;
-    }
-
-    private static class ToastEntry {
-        public final String packageName;
-        public final IBinder token;
-        public final View view;
-        public final IBinder windowToken;
-
-        @Nullable
-        public final ITransientNotificationCallback callback;
-
-        private ToastEntry(String packageName, IBinder token, View view, IBinder windowToken,
-                @Nullable ITransientNotificationCallback callback) {
-            this.packageName = packageName;
-            this.token = token;
-            this.view = view;
-            this.windowToken = windowToken;
-            this.callback = callback;
-        }
+        mPresenter.hide(mCallback);
+        mPresenter = null;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index eecde72..73f9d8a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -590,7 +590,8 @@
         when(mPackageManager.resolveService(any(Intent.class), eq(0))).thenReturn(resolveInfo);
         when(mDevicePolicyManager.isSecondaryLockscreenEnabled(eq(UserHandle.of(user))))
                 .thenReturn(true, false);
-        when(mDevicePolicyManager.getProfileOwnerAsUser(user))
+        when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+                UserHandle.of(user)))
                 .thenReturn(new ComponentName(packageName, cls));
 
         // Initially null.
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/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 7821ae2..847e442 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -91,7 +91,7 @@
         fakeExecutor = FakeExecutor(FakeSystemClock())
 
         userBroadcastDispatcher = UserBroadcastDispatcher(
-                mockContext, USER_ID, handler, testableLooper.looper)
+                mockContext, USER_ID, testableLooper.looper)
         userBroadcastDispatcher.pendingResult = mPendingResult
     }
 
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..037f04e 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 c86b5f7..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");
@@ -599,13 +602,13 @@
         sendUpdatedEntryAtTime(mEntryA1, 1000);
         sendUpdatedEntryAtTime(mEntryA2, 2000);
         sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
-        changeExpandedStateAtTime(true, 4000L);
+        changeExpandedStateAtTime(true, 4000L); // B1 marked updated at 4000L
         mBubbleData.setListener(mListener);
 
         // Test
         sendUpdatedEntryAtTime(mEntryC1, 4000);
         verifyUpdateReceived();
-        assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1);
+        assertOrderChangedTo(mBubbleB1, mBubbleC1, mBubbleA2, mBubbleA1);
     }
 
     /**
@@ -789,8 +792,7 @@
      * When the stack transitions to the collapsed state, the selected bubble is brought to the top.
      * Bubbles within the same group should move up with it.
      * <p>
-     * When the stack transitions back to the expanded state, the previous ordering is restored, as
-     * long as no changes have been made (adds, removes or updates) while in the collapsed state.
+     * When the stack transitions back to the expanded state, this new order is kept as is.
      */
     @Test
     public void test_expansionChanges() {
@@ -813,20 +815,12 @@
         // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
         // to moving the selected bubble to the top (first).
         //
-        // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
-        //
-        // That state is restored as long as no changes occur (add/remove/update) while in
-        // the collapsed state.
+        // In this case, the expected re-expand state will be: [A2, A1, B1, B2]
         //
         // collapse -> selected bubble (A2) moves first.
         changeExpandedStateAtTime(false, 8000L);
         verifyUpdateReceived();
         assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
-
-        // expand -> "original" order/grouping restored
-        changeExpandedStateAtTime(true, 10000L);
-        verifyUpdateReceived();
-        assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
     }
 
     /**
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/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index fa02231..8a412bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.carrier;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -219,4 +220,18 @@
                 mock(NetworkController.IconState.class),
                 0, 0, true, true, "", "", "", true, 0, true);
     }
+
+    @Test
+    public void testNoEmptyVisibleView_airplaneMode() {
+        CarrierTextController.CarrierTextCallbackInfo
+                info = new CarrierTextController.CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{""},
+                true,
+                new int[]{0},
+                true /* airplaneMode */);
+        mCallback.updateCarrierInfo(info);
+        mTestableLooper.processAllMessages();
+        assertEquals(View.GONE, mQSCarrierGroup.getNoSimTextView().getVisibility());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 1e3636b..6b7a3bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -43,6 +44,7 @@
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
@@ -128,6 +130,23 @@
     }
 
     @Test
+    fun updateBlurCallback_setsBlur_whenExpanded() {
+        `when`(shadeSpring.radius).thenReturn(maxBlur)
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+        verify(blurUtils).applyBlur(any(), eq(maxBlur))
+    }
+
+    @Test
+    fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
+        `when`(shadeSpring.radius).thenReturn(maxBlur)
+        val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+        animProgress.linearProgress = 1f
+        notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+        verify(blurUtils).applyBlur(any(), eq(0))
+    }
+
+    @Test
     fun updateBlurCallback_invalidWindow() {
         doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
                 .setWallpaperZoomOut(any(), anyFloat())
@@ -159,6 +178,24 @@
         verify(blurUtils).applyBlur(safeEq(viewRootImpl), eq(0))
     }
 
+    @Test
+    fun setNotificationLaunchAnimationParams_schedulesFrame() {
+        val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+        animProgress.linearProgress = 0.5f
+        notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+        verify(choreographer).postFrameCallback(
+                eq(notificationShadeDepthController.updateBlurCallback))
+    }
+
+    @Test
+    fun setNotificationLaunchAnimationParams_whennNull_ignoresIfShadeHasNoBlur() {
+        val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+        animProgress.linearProgress = 0.5f
+        `when`(shadeSpring.radius).thenReturn(0)
+        notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+        verify(shadeSpring, never()).animateTo(anyInt(), any())
+    }
+
     private fun <T : Any> safeEq(value: T): T {
         return eq(value) ?: value
     }
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..ea68516 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -99,7 +99,7 @@
         mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
 
-        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/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index a07cfc3..cdef49d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -19,7 +19,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -31,6 +30,7 @@
 import android.view.View;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -39,8 +39,12 @@
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -48,14 +52,22 @@
 public class ActivityLaunchAnimatorTest extends SysuiTestCase {
 
     private ActivityLaunchAnimator mLaunchAnimator;
-    private ActivityLaunchAnimator.Callback mCallback = mock(ActivityLaunchAnimator.Callback.class);
-    private NotificationShadeWindowViewController mNotificationShadeWindowViewController = mock(
-            NotificationShadeWindowViewController.class);
-    private NotificationShadeWindowView mNotificationShadeWindowView = mock(
-            NotificationShadeWindowView.class);
-    private NotificationListContainer mNotificationContainer
-            = mock(NotificationListContainer.class);
-    private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
+    @Mock
+    private ActivityLaunchAnimator.Callback mCallback;
+    @Mock
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    @Mock
+    private NotificationShadeWindowView mNotificationShadeWindowView;
+    @Mock
+    private NotificationListContainer mNotificationContainer;
+    @Mock
+    private ExpandableNotificationRow mRow;
+    @Mock
+    private NotificationShadeDepthController mNotificationShadeDepthController;
+    @Mock
+    private NotificationPanelViewController mNotificationPanelViewController;
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     @Before
     public void setUp() throws Exception {
@@ -66,7 +78,8 @@
         mLaunchAnimator = new ActivityLaunchAnimator(
                 mNotificationShadeWindowViewController,
                 mCallback,
-                mock(NotificationPanelViewController.class),
+                mNotificationPanelViewController,
+                mNotificationShadeDepthController,
                 mNotificationContainer);
 
     }
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/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index f4fbd7b..43cf83f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -57,7 +57,7 @@
 
         /* ListEntry properties */
         entry.setParent(mParent);
-        entry.setSection(mSection);
+        entry.getAttachState().setSectionIndex(mSection);
         return entry;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index d7c7279..3adc3d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -417,8 +417,8 @@
         );
 
         // THEN each filtered notif records the NotifFilter that did it
-        assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
-        assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
+        assertEquals(preGroupFilter, mEntrySet.get(1).getExcludingFilter());
+        assertEquals(preGroupFilter, mEntrySet.get(3).getExcludingFilter());
     }
 
     @Test
@@ -447,8 +447,8 @@
         );
 
         // THEN each filtered notif records the filter that did it
-        assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
-        assertEquals(filter1, mEntrySet.get(3).mExcludingFilter);
+        assertEquals(filter1, mEntrySet.get(1).getExcludingFilter());
+        assertEquals(filter1, mEntrySet.get(3).getExcludingFilter());
     }
 
     @Test
@@ -471,7 +471,7 @@
         );
 
         // THEN each filtered notif records the filter that did it
-        assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
+        assertEquals(filter1, mEntrySet.get(0).getExcludingFilter());
     }
 
     @Test
@@ -502,8 +502,8 @@
         );
 
         // THEN each filtered notif records the filter that did it
-        assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
-        assertEquals(filter2, mEntrySet.get(2).mExcludingFilter);
+        assertEquals(filter1, mEntrySet.get(1).getExcludingFilter());
+        assertEquals(filter2, mEntrySet.get(2).getExcludingFilter());
     }
 
     @Test
@@ -541,8 +541,8 @@
         );
 
         // THEN each promoted notif records the promoter that did it
-        assertEquals(promoter, mEntrySet.get(2).mNotifPromoter);
-        assertEquals(promoter, mEntrySet.get(3).mNotifPromoter);
+        assertEquals(promoter, mEntrySet.get(2).getNotifPromoter());
+        assertEquals(promoter, mEntrySet.get(3).getNotifPromoter());
     }
 
     @Test
@@ -572,8 +572,8 @@
         verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(3));
 
         // THEN each promoter is recorded on each notif it promoted
-        assertEquals(promoter1, mEntrySet.get(2).mNotifPromoter);
-        assertEquals(promoter2, mEntrySet.get(3).mNotifPromoter);
+        assertEquals(promoter1, mEntrySet.get(2).getNotifPromoter());
+        assertEquals(promoter2, mEntrySet.get(3).getNotifPromoter());
     }
 
     @Test
@@ -650,34 +650,34 @@
         verify(pkg5Section).isInSection(mEntrySet.get(9));
 
         // THEN the correct section is assigned for entries in pkg1Section
-        assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
+        assertEquals(pkg1Section, mEntrySet.get(2).getNotifSection());
         assertEquals(0, mEntrySet.get(2).getSection());
-        assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
+        assertEquals(pkg1Section, mEntrySet.get(7).getNotifSection());
         assertEquals(0, mEntrySet.get(7).getSection());
 
         // THEN the correct section is assigned for entries in pkg2Section
-        assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
+        assertEquals(pkg2Section, mEntrySet.get(1).getNotifSection());
         assertEquals(1, mEntrySet.get(1).getSection());
-        assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
+        assertEquals(pkg2Section, mEntrySet.get(8).getNotifSection());
         assertEquals(1, mEntrySet.get(8).getSection());
-        assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
+        assertEquals(pkg2Section, mBuiltList.get(3).getNotifSection());
         assertEquals(1, mBuiltList.get(3).getSection());
 
         // THEN no section was assigned to entries in pkg4Section (since they were filtered)
-        assertEquals(null, mEntrySet.get(0).mNotifSection);
+        assertEquals(null, mEntrySet.get(0).getNotifSection());
         assertEquals(-1, mEntrySet.get(0).getSection());
-        assertEquals(null, mEntrySet.get(10).mNotifSection);
+        assertEquals(null, mEntrySet.get(10).getNotifSection());
         assertEquals(-1, mEntrySet.get(10).getSection());
 
 
         // THEN the correct section is assigned for entries in pkg5Section
-        assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
+        assertEquals(pkg5Section, mEntrySet.get(9).getNotifSection());
         assertEquals(3, mEntrySet.get(9).getSection());
 
         // THEN the children entries are assigned the same section as its parent
-        assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
+        assertEquals(mBuiltList.get(3).getNotifSection(), child(5).entry.getNotifSection());
         assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
-        assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
+        assertEquals(mBuiltList.get(3).getNotifSection(), child(6).entry.getNotifSection());
         assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
     }
 
@@ -700,7 +700,7 @@
 
         // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
         assertEquals(1, notif(0).entry.getSection());
-        assertNotNull(notif(0).entry.mNotifSection);
+        assertNotNull(notif(0).entry.getNotifSection());
     }
 
     @Test
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/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index bdd7a2e..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
@@ -131,6 +131,7 @@
 
     @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
     @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
+    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     private StatusBarNotification mSbn;
     private NotificationListenerService.RankingMap mRankingMap;
@@ -181,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,
@@ -239,7 +241,8 @@
                                 mGutsManager,
                                 true,
                                 null,
-                                mFalsingManager
+                                mFalsingManager,
+                                mPeopleNotificationIdentifier
                         ));
 
         when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
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 5ad88c9..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
@@ -72,6 +72,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -118,6 +119,7 @@
     @Mock private INotificationManager mINotificationManager;
     @Mock private LauncherApps mLauncherApps;
     @Mock private ShortcutManager mShortcutManager;
+    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     @Before
     public void setUp() {
@@ -129,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,
@@ -465,7 +467,8 @@
     }
 
     private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) {
-        NotificationMenuRowPlugin menuRow = new NotificationMenuRow(mContext);
+        NotificationMenuRowPlugin menuRow =
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         menuRow.createMenu(row, row.getEntry().getSbn());
 
         NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index b33d26f..99e8c7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.After;
@@ -54,11 +55,13 @@
 public class NotificationMenuRowTest extends LeakCheckedTest {
 
     private ExpandableNotificationRow mRow;
+    private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     @Before
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mRow = mock(ExpandableNotificationRow.class);
+        mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
         NotificationEntry entry = new NotificationEntryBuilder().build();
         when(mRow.getEntry()).thenReturn(entry);
     }
@@ -71,7 +74,8 @@
 
     @Test
     public void testAttachDetach() {
-        NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+        NotificationMenuRowPlugin row =
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
         ViewUtils.attachView(row.getMenuView());
         TestableLooper.get(this).processAllMessages();
@@ -81,7 +85,8 @@
 
     @Test
     public void testRecreateMenu() {
-        NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+        NotificationMenuRowPlugin row =
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
         assertTrue(row.getMenuView() != null);
         row.createMenu(mRow, null);
@@ -90,7 +95,8 @@
 
     @Test
     public void testResetUncreatedMenu() {
-        NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+        NotificationMenuRowPlugin row =
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.resetMenu();
     }
 
@@ -99,7 +105,7 @@
     public void testNoAppOpsInSlowSwipe() {
         Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
 
-        NotificationMenuRow row = new NotificationMenuRow(mContext);
+        NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
 
         ViewGroup container = (ViewGroup) row.getMenuView();
@@ -111,7 +117,7 @@
     public void testNoSnoozeInSlowSwipe() {
         Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
 
-        NotificationMenuRow row = new NotificationMenuRow(mContext);
+        NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
 
         ViewGroup container = (ViewGroup) row.getMenuView();
@@ -123,7 +129,7 @@
     public void testSnoozeInSlowSwipe() {
         Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
 
-        NotificationMenuRow row = new NotificationMenuRow(mContext);
+        NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
 
         ViewGroup container = (ViewGroup) row.getMenuView();
@@ -133,7 +139,8 @@
 
     @Test
     public void testIsSnappedAndOnSameSide() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
 
         when(row.isMenuVisible()).thenReturn(true);
         when(row.isMenuSnapped()).thenReturn(true);
@@ -165,7 +172,8 @@
 
     @Test
     public void testGetMenuSnapTarget() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         when(row.isMenuOnLeft()).thenReturn(true);
         doReturn(30).when(row).getSpaceForMenu();
 
@@ -179,7 +187,8 @@
 
     @Test
     public void testIsSwipedEnoughToShowMenu() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         when(row.isMenuVisible()).thenReturn(true);
         when(row.isMenuOnLeft()).thenReturn(true);
         doReturn(40f).when(row).getMinimumSwipeDistance();
@@ -205,7 +214,8 @@
 
     @Test
     public void testIsWithinSnapMenuThreshold() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         doReturn(30f).when(row).getSnapBackThreshold();
         doReturn(50f).when(row).getDismissThreshold();
 
@@ -238,7 +248,8 @@
 
     @Test
     public void testShouldSnapBack() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         doReturn(40f).when(row).getSnapBackThreshold();
         when(row.isMenuVisible()).thenReturn(false);
         when(row.isMenuOnLeft()).thenReturn(true);
@@ -259,7 +270,8 @@
 
     @Test
     public void testCanBeDismissed() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class);
 
         when(row.getParent()).thenReturn(parent);
@@ -274,7 +286,8 @@
 
     @Test
     public void testIsTowardsMenu() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         when(row.isMenuVisible()).thenReturn(true);
         when(row.isMenuOnLeft()).thenReturn(true);
 
@@ -294,7 +307,8 @@
 
     @Test
     public void onSnapBack() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
                 .OnMenuEventListener.class);
         row.setMenuClickListener(listener);
@@ -315,7 +329,8 @@
 
     @Test
     public void testOnSnap() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         when(row.isMenuOnLeft()).thenReturn(true);
         NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
                 .OnMenuEventListener.class);
@@ -335,7 +350,8 @@
 
     @Test
     public void testOnDismiss() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         doNothing().when(row).cancelDrag();
         row.onSnapOpen();
 
@@ -351,7 +367,8 @@
 
     @Test
     public void testOnDown() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         doNothing().when(row).beginDrag();
 
         row.onTouchStart();
@@ -361,7 +378,8 @@
 
     @Test
     public void testOnUp() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         row.onTouchStart();
 
         assertTrue("before onTouchEnd, isUserTouching is true", row.isUserTouching());
@@ -373,7 +391,8 @@
 
     @Test
     public void testIsMenuVisible() {
-        NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+        NotificationMenuRow row = Mockito.spy(
+                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
         row.setMenuAlpha(0);
 
         assertFalse("when alpha is 0, menu is not visible", row.isMenuVisible());
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 2134a3d..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;
@@ -57,6 +58,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
 import com.android.systemui.statusbar.notification.icon.IconManager;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -91,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;
@@ -100,9 +103,14 @@
     private final RowContentBindStage mBindStage;
     private final IconManager mIconManager;
     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);
@@ -131,13 +139,17 @@
 
         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 =
                 ArgumentCaptor.forClass(NotifCollectionListener.class);
         verify(collection).addCollectionListener(collectionListenerCaptor.capture());
         mBindPipelineEntryListener = collectionListenerCaptor.getValue();
+        mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
     }
 
     /**
@@ -407,10 +419,11 @@
                 mock(NotificationMediaManager.class),
                 mock(ExpandableNotificationRow.OnAppOpsClickListener.class),
                 mock(FalsingManager.class),
-                mStatusBarStateController);
+                mStatusBarStateController,
+                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
@@ -419,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/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index b5f57b6..962d773 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -39,6 +39,7 @@
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.provider.Settings;
@@ -101,6 +102,7 @@
     protected NetworkRegistrationInfo mFakeRegInfo;
     protected ConnectivityManager mMockCm;
     protected WifiManager mMockWm;
+    protected NetworkScoreManager mMockNsm;
     protected SubscriptionManager mMockSm;
     protected TelephonyManager mMockTm;
     protected BroadcastDispatcher mMockBd;
@@ -148,6 +150,7 @@
         mMockSm = mock(SubscriptionManager.class);
         mMockCm = mock(ConnectivityManager.class);
         mMockBd = mock(BroadcastDispatcher.class);
+        mMockNsm = mock(NetworkScoreManager.class);
         mMockSubDefaults = mock(SubscriptionDefaults.class);
         mNetCapabilities = new NetworkCapabilities();
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
@@ -196,8 +199,8 @@
             return null;
         }).when(mMockProvisionController).addCallback(any());
 
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+                mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mMockProvisionController, mMockBd);
         setupNetworkController();
@@ -244,18 +247,17 @@
     }
 
     protected NetworkControllerImpl setUpNoMobileData() {
-      when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
-      NetworkControllerImpl networkControllerNoMobile
-              = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+        when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+        NetworkControllerImpl networkControllerNoMobile =
+                new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm,
                         mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
                         mock(DataUsageController.class), mMockSubDefaults,
                         mock(DeviceProvisionedController.class), mMockBd);
 
-      setupNetworkController();
+        setupNetworkController();
 
-      return networkControllerNoMobile;
-
+        return networkControllerNoMobile;
     }
 
     // 2 Bars 3G GSM.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 75f2619..6fffcff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -102,8 +102,8 @@
     public void test4gDataIcon() {
         // Switch to showing 4g icon and re-initialize the NetworkController.
         mConfig.show4gForLte = true;
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, Looper.getMainLooper(), mCallbackHandler,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+                mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
                 mock(DataUsageController.class), mMockSubDefaults,
                 mock(DeviceProvisionedController.class), mMockBd);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index b922f06..399b5c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -58,8 +58,8 @@
         // Turn off mobile network support.
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, Looper.getMainLooper(), mCallbackHandler,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+                mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
         setupNetworkController();
@@ -120,8 +120,8 @@
         // Turn off mobile network support.
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, Looper.getMainLooper(), mCallbackHandler,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+                mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
         setupNetworkController();
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 86add98..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/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index bc3a5b1..65fbe79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -176,7 +176,7 @@
 
         mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
 
-        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
     }
 
     @Test
@@ -218,7 +218,7 @@
         mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);
 
         verify(mWindowManager).removeViewImmediate(view);
-        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
         verify(mCallback).onToastHidden();
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index e942b27..55a9296 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -645,33 +645,21 @@
     }
 
     /**
-     * Returns whether inline suggestions are enabled for Autofill.
+     * Returns whether inline suggestions are supported by Autofill provider (not augmented
+     * Autofill provider).
      */
-    private boolean isInlineSuggestionsEnabledLocked() {
-        return mService.isInlineSuggestionsEnabled()
-                || mService.getRemoteInlineSuggestionRenderServiceLocked() != null;
+    private boolean isInlineSuggestionsEnabledByAutofillProviderLocked() {
+        return mService.isInlineSuggestionsEnabled();
     }
 
-    /**
-     * Ask the IME to make an inline suggestions request if enabled.
-     */
-    private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
-            int newState, int flags) {
-        if (isInlineSuggestionsEnabledLocked()) {
-            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
-                    mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
-            if (inlineSuggestionsRequestConsumer != null) {
-                mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
-                        inlineSuggestionsRequestConsumer);
-            }
-        } else {
-            mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
-        }
-        requestNewFillResponseLocked(viewState, newState, flags);
+    private boolean isInlineSuggestionRenderServiceAvailable() {
+        return mService.getRemoteInlineSuggestionRenderServiceLocked() != null;
     }
 
     /**
      * Reads a new structure and then request a new fill response from the fill service.
+     *
+     * <p> Also asks the IME to make an inline suggestions request if it's enabled.
      */
     @GuardedBy("mLock")
     private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
@@ -717,6 +705,21 @@
         // structure is taken. This causes only one fill request per bust of focus changes.
         cancelCurrentRequestLocked();
 
+        // Only ask IME to create inline suggestions request if Autofill provider supports it and
+        // the render service is available.
+        if (isInlineSuggestionsEnabledByAutofillProviderLocked()
+                && isInlineSuggestionRenderServiceAvailable()) {
+            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
+                    mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
+            if (inlineSuggestionsRequestConsumer != null) {
+                mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
+                        inlineSuggestionsRequestConsumer);
+            }
+        } else {
+            mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
+        }
+
+        // Now request the assist structure data.
         try {
             final Bundle receiverExtras = new Bundle();
             receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
@@ -2322,8 +2325,7 @@
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             mForAugmentedAutofillOnly = false;
             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
-            maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
-                    ViewState.STATE_RESTARTED_SESSION, flags);
+            requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
             return true;
         }
 
@@ -2333,8 +2335,7 @@
                 Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
                         + viewState.getStateAsString());
             }
-            maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
-                    ViewState.STATE_STARTED_PARTITION, flags);
+            requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
             return true;
         } else {
             if (sVerbose) {
@@ -2458,8 +2459,7 @@
                 // View is triggering autofill.
                 mCurrentViewId = viewState.id;
                 viewState.update(value, virtualBounds, flags);
-                maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
-                        ViewState.STATE_STARTED_SESSION, flags);
+                requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
                 break;
             case ACTION_VALUE_CHANGED:
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -3107,12 +3107,15 @@
                             }, mService.getRemoteInlineSuggestionRenderServiceLocked());
                 };
 
-        // There are 3 cases when augmented autofill should ask IME for a new request:
-        // 1. standard autofill provider is None
-        // 2. standard autofill provider doesn't support inline (and returns null response)
-        // 3. standard autofill provider supports inline, but isn't called because the field
-        // doesn't want autofill
-        if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabledLocked()) {
+        // When the inline suggestion render service is available, there are 2 cases when
+        // augmented autofill should ask IME for inline suggestion request, because standard
+        // autofill flow didn't:
+        // 1. the field is augmented autofill only (when standard autofill provider is None or
+        // when it returns null response)
+        // 2. standard autofill provider doesn't support inline suggestion
+        if (isInlineSuggestionRenderServiceAvailable()
+                && (mForAugmentedAutofillOnly
+                || !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
             mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
                     /*requestConsumer=*/ requestAugmentedAutofill);
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 808d322..bfcde97 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -98,8 +98,8 @@
 
     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
-
-    private static final String DEVICE_CONFIG_DISABLE_FLAG = "disable_rescue_party";
+    private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+            "persist.device_config.configuration.disable_rescue_party";
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -118,8 +118,7 @@
 
         // We're disabled if the DeviceConfig disable flag is set to true.
         // This is in case that an emergency rollback of the feature is needed.
-        if (DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_CONFIGURATION, DEVICE_CONFIG_DISABLE_FLAG, false)) {
+        if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
             Slog.v(TAG, "Disabled because of DeviceConfig flag");
             return true;
         }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9018caa..0671477 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -234,6 +234,7 @@
     private static final String FUSE_ENABLED = "fuse_enabled";
     private static final boolean DEFAULT_FUSE_ENABLED = true;
 
+    @GuardedBy("mLock")
     private final Set<Integer> mFuseMountedUser = new ArraySet<>();
 
     public static class Lifecycle extends SystemService {
@@ -810,7 +811,7 @@
                 }
                 case H_VOLUME_STATE_CHANGED: {
                     final SomeArgs args = (SomeArgs) msg.obj;
-                    onVolumeStateChangedInternal((VolumeInfo) args.arg1, (int) args.arg2,
+                    onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2,
                             (int) args.arg3);
                 }
             }
@@ -1337,6 +1338,7 @@
                     args.arg2 = oldState;
                     args.arg3 = newState;
                     mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
+                    onVolumeStateChangedLocked(vol, oldState, newState);
                 }
             }
         }
@@ -1509,11 +1511,45 @@
         return true;
     }
 
-    private void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) {
-        synchronized (mLock) {
-            if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) {
+
+    private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
+        if (vol.type == VolumeInfo.TYPE_EMULATED) {
+            if (newState != VolumeInfo.STATE_MOUNTED) {
                 mFuseMountedUser.remove(vol.getMountUserId());
+            } else {
+                final int userId = vol.getMountUserId();
+                mFuseMountedUser.add(userId);
+                // Async remount app storage so it won't block the main thread.
+                new Thread(() -> {
+                    Map<Integer, String> pidPkgMap = null;
+                    // getProcessesWithPendingBindMounts() could fail when a new app process is
+                    // starting and it's not planning to mount storage dirs in zygote, but it's
+                    // rare, so we retry 5 times and hope we can get the result successfully.
+                    for (int i = 0; i < 5; i++) {
+                        try {
+                            pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
+                                    .getProcessesWithPendingBindMounts(vol.getMountUserId());
+                            break;
+                        } catch (IllegalStateException e) {
+                            Slog.i(TAG, "Some processes are starting, retry");
+                            // Wait 100ms and retry so hope the pending process is started.
+                            SystemClock.sleep(100);
+                        }
+                    }
+                    if (pidPkgMap != null) {
+                        remountAppStorageDirs(pidPkgMap, userId);
+                    } else {
+                        Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
+                                + " 5 retries");
+                    }
+                }).start();
             }
+        }
+    }
+
+
+    private void onVolumeStateChangedAsync(VolumeInfo vol, int oldState, int newState) {
+        synchronized (mLock) {
             // Remember that we saw this volume so we're ready to accept user
             // metadata, or so we can annoy them when a private volume is ejected
             if (!TextUtils.isEmpty(vol.fsUuid)) {
@@ -2161,35 +2197,6 @@
                 }
             });
             Slog.i(TAG, "Mounted volume " + vol);
-            if (vol.type == VolumeInfo.TYPE_EMULATED) {
-                final int userId = vol.getMountUserId();
-                mFuseMountedUser.add(userId);
-                // Async remount app storage so it won't block the main thread.
-                new Thread(() -> {
-                    Map<Integer, String> pidPkgMap = null;
-                    // getProcessesWithPendingBindMounts() could fail when a new app process is
-                    // starting and it's not planning to mount storage dirs in zygote, but it's
-                    // rare, so we retry 5 times and hope we can get the result successfully.
-                    for (int i = 0; i < 5; i++) {
-                        try {
-                            pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
-                                    .getProcessesWithPendingBindMounts(vol.getMountUserId());
-                            break;
-                        } catch (IllegalStateException e) {
-                            Slog.i(TAG, "Some processes are starting, retry");
-                            // Wait 100ms and retry so hope the pending process is started.
-                            SystemClock.sleep(100);
-                        }
-                    }
-                    if (pidPkgMap != null) {
-                        remountAppStorageDirs(pidPkgMap, userId);
-                    } else {
-                        Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
-                                + " 5 retries");
-                    }
-
-                }).start();
-            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -4445,9 +4452,11 @@
         @Override
         public boolean prepareStorageDirs(int userId, Set<String> packageList,
                 String processName) {
-            if (!mFuseMountedUser.contains(userId)) {
-                Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
-                return false;
+            synchronized (mLock) {
+                if (!mFuseMountedUser.contains(userId)) {
+                    Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
+                    return false;
+                }
             }
             try {
                 final IVold vold = IVold.Stub.asInterface(
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 059eb6a..df16058 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -32,7 +32,7 @@
             "name": "CtsWindowManagerDeviceTestCases",
             "options": [
                 {
-                    "include-filter": "android.server.wm.ToastTest"
+                    "include-filter": "android.server.wm.ToastWindowTest"
                 }
             ],
             "file_patterns": ["NotificationManagerService\\.java"]
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 689f64d0..85d28831 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17698,7 +17698,7 @@
 
             proc.setReportedForegroundServiceTypes(fgServiceTypes);
             ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.pid, proc.info.uid);
-            item.changes = ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
+            item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
             item.foregroundServiceTypes = fgServiceTypes;
         }
         if (oomAdj) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 1412112..dbcb3da 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2385,7 +2385,7 @@
                     "Changes in " + app + ": " + changes);
             ActivityManagerService.ProcessChangeItem item =
                     mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
-            item.changes = changes;
+            item.changes |= changes;
             item.foregroundActivities = app.repForegroundActivities;
             item.capability = app.setCapability;
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f6cdaeb..30e765f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1331,10 +1331,10 @@
     private void updateDefaultVolumes() {
         for (int stream = 0; stream < mStreamStates.length; stream++) {
             if (stream != mStreamVolumeAlias[stream]) {
-                AudioSystem.DEFAULT_STREAM_VOLUME[stream] = rescaleIndex(
-                        AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]],
+                AudioSystem.DEFAULT_STREAM_VOLUME[stream] = (rescaleIndex(
+                        AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]] * 10,
                         mStreamVolumeAlias[stream],
-                        stream);
+                        stream) + 5) / 10;
             }
         }
     }
@@ -2332,8 +2332,7 @@
     }
 
     private void enforceModifyAudioRoutingPermission() {
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
         }
@@ -4610,6 +4609,117 @@
         observeDevicesForStreams(-1);
     }
 
+    /**
+     * @see AudioManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     * @param device the audio device to be affected
+     * @param deviceVolumeBehavior one of the device behaviors
+     */
+    public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+            @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) {
+        // verify permissions
+        enforceModifyAudioRoutingPermission();
+        // verify arguments
+        Objects.requireNonNull(device);
+        AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
+        if (pkgName == null) {
+            pkgName = "";
+        }
+        // translate Java device type to native device type (for the devices masks for full / fixed)
+        final int type;
+        switch (device.getType()) {
+            case AudioDeviceInfo.TYPE_HDMI:
+                type = AudioSystem.DEVICE_OUT_HDMI;
+                break;
+            case AudioDeviceInfo.TYPE_HDMI_ARC:
+                type = AudioSystem.DEVICE_OUT_HDMI_ARC;
+                break;
+            case AudioDeviceInfo.TYPE_LINE_DIGITAL:
+                type = AudioSystem.DEVICE_OUT_SPDIF;
+                break;
+            case AudioDeviceInfo.TYPE_AUX_LINE:
+                type = AudioSystem.DEVICE_OUT_LINE;
+                break;
+            default:
+                // unsupported for now
+                throw new IllegalArgumentException("Unsupported device type " + device.getType());
+        }
+        // update device masks based on volume behavior
+        switch (deviceVolumeBehavior) {
+            case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE:
+                mFullVolumeDevices.remove(type);
+                mFixedVolumeDevices.remove(type);
+                break;
+            case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED:
+                mFullVolumeDevices.remove(type);
+                mFixedVolumeDevices.add(type);
+                break;
+            case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL:
+                mFullVolumeDevices.add(type);
+                mFixedVolumeDevices.remove(type);
+                break;
+            case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+            case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+                throw new IllegalArgumentException("Absolute volume unsupported for now");
+        }
+        // log event and caller
+        sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "Volume behavior " + deviceVolumeBehavior
+                        + " for dev=0x" + Integer.toHexString(type) + " by pkg:" + pkgName));
+        // make sure we have a volume entry for this device, and that volume is updated according
+        // to volume behavior
+        checkAddAllFixedVolumeDevices(type, "setDeviceVolumeBehavior:" + pkgName);
+    }
+
+    /**
+     * @see AudioManager#getDeviceVolumeBehavior(AudioDeviceAttributes)
+     * @param device the audio output device type
+     * @return the volume behavior for the device
+     */
+    public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior(
+            @NonNull AudioDeviceAttributes device) {
+        // verify permissions
+        enforceModifyAudioRoutingPermission();
+        // translate Java device type to native device type (for the devices masks for full / fixed)
+        final int type;
+        switch (device.getType()) {
+            case AudioDeviceInfo.TYPE_HEARING_AID:
+                type = AudioSystem.DEVICE_OUT_HEARING_AID;
+                break;
+            case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+                type = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+                break;
+            case AudioDeviceInfo.TYPE_HDMI:
+                type = AudioSystem.DEVICE_OUT_HDMI;
+                break;
+            case AudioDeviceInfo.TYPE_HDMI_ARC:
+                type = AudioSystem.DEVICE_OUT_HDMI_ARC;
+                break;
+            case AudioDeviceInfo.TYPE_LINE_DIGITAL:
+                type = AudioSystem.DEVICE_OUT_SPDIF;
+                break;
+            case AudioDeviceInfo.TYPE_AUX_LINE:
+                type = AudioSystem.DEVICE_OUT_LINE;
+                break;
+            default:
+                // unsupported for now
+                throw new IllegalArgumentException("Unsupported device type " + device.getType());
+        }
+        if ((mFullVolumeDevices.contains(type))) {
+            return AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL;
+        }
+        if ((mFixedVolumeDevices.contains(type))) {
+            return AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED;
+        }
+        if ((mAbsVolumeMultiModeCaseDevices.contains(type))) {
+            return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
+        }
+        if (type == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
+                && mDeviceBroker.isAvrcpAbsoluteVolumeSupported()) {
+            return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+        }
+        return AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE;
+    }
+
     /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0;
     /*package*/ static final int CONNECTION_STATE_CONNECTED = 1;
     /**
@@ -4779,7 +4889,9 @@
             } catch (IllegalArgumentException e) {
                 // Volume Groups without attributes are not controllable through set/get volume
                 // using attributes. Do not append them.
-                Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+                }
                 continue;
             }
             sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
@@ -4800,7 +4912,9 @@
     }
 
     private void readVolumeGroupsSettings() {
-        Log.v(TAG, "readVolumeGroupsSettings");
+        if (DEBUG_VOL) {
+            Log.v(TAG, "readVolumeGroupsSettings");
+        }
         for (int i = 0; i < sVolumeGroupStates.size(); i++) {
             final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
             vgs.readSettings();
@@ -4810,7 +4924,9 @@
 
     // Called upon crash of AudioServer
     private void restoreVolumeGroups() {
-        Log.v(TAG, "restoreVolumeGroups");
+        if (DEBUG_VOL) {
+            Log.v(TAG, "restoreVolumeGroups");
+        }
         for (int i = 0; i < sVolumeGroupStates.size(); i++) {
             final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
             vgs.applyAllVolumes();
@@ -4846,7 +4962,9 @@
 
         private VolumeGroupState(AudioVolumeGroup avg) {
             mAudioVolumeGroup = avg;
-            Log.v(TAG, "VolumeGroupState for " + avg.toString());
+            if (DEBUG_VOL) {
+                Log.v(TAG, "VolumeGroupState for " + avg.toString());
+            }
             for (final AudioAttributes aa : avg.getAudioAttributes()) {
                 if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
                     mAudioAttributes = aa;
@@ -4949,16 +5067,21 @@
                     final int device = mIndexMap.keyAt(i);
                     if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
                         index = mIndexMap.valueAt(i);
-                        Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
-                                + mAudioVolumeGroup.name() + " and device "
-                                + AudioSystem.getOutputDeviceName(device));
+                        if (DEBUG_VOL) {
+                            Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+                                    + mAudioVolumeGroup.name() + " and device "
+                                    + AudioSystem.getOutputDeviceName(device));
+                        }
                         setVolumeIndexInt(index, device, 0 /*flags*/);
                     }
                 }
                 // apply default volume last: by convention , default device volume will be used
+                // by audio policy manager if no explicit volume is present for a given device type
                 index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
-                Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
-                        + mAudioVolumeGroup.name());
+                if (DEBUG_VOL) {
+                    Log.v(TAG, "applyAllVolumes: restore default device index " + index
+                            + " for group " + mAudioVolumeGroup.name());
+                }
                 setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
             }
         }
@@ -4967,9 +5090,11 @@
             if (mUseFixedVolume) {
                 return;
             }
-            Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
-                    + mAudioVolumeGroup.name() + " and device "
-                    + AudioSystem.getOutputDeviceName(device));
+            if (DEBUG_VOL) {
+                Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+                        + mAudioVolumeGroup.name() + " and device "
+                        + AudioSystem.getOutputDeviceName(device));
+            }
             boolean success = Settings.System.putIntForUser(mContentResolver,
                     getSettingNameForDevice(device),
                     getIndex(device),
@@ -4999,12 +5124,12 @@
                     index = Settings.System.getIntForUser(
                             mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
                     if (index == -1) {
-                        Log.e(TAG, "readSettings: No index stored for group "
-                                + mAudioVolumeGroup.name() + ", device " + name);
                         continue;
                     }
-                    Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
-                             + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+                    if (DEBUG_VOL) {
+                        Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+                                 + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+                    }
                     mIndexMap.put(device, getValidIndex(index));
                 }
             }
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 65f2218..32c6cc3 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -150,14 +150,14 @@
         final AudioRecordingConfiguration config = createRecordingConfiguration(
                 uid, session, source, recordingInfo,
                 portId, silenced, activeSource, clientEffects, effects);
-        if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
+        if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX
+                && (event == AudioManager.RECORD_CONFIG_EVENT_START
+                        || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE)) {
             final AudioDeviceInfo device = config.getAudioDevice();
-            if (AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
+            if (device != null
+                    && AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
                 mLegacyRemoteSubmixRiid.set(riid);
-                if (event == AudioManager.RECORD_CONFIG_EVENT_START
-                        || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE) {
-                    mLegacyRemoteSubmixActive.set(true);
-                }
+                mLegacyRemoteSubmixActive.set(true);
             }
         }
         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
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..ccd8848 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/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index b28350d..52e9d7c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -54,11 +54,9 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -70,10 +68,8 @@
     private static final String TAG = "MR2ServiceImpl";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    /**
-     * TODO: Change this with the real request ID from MediaRouter2 when
-     * MediaRouter2 needs to get notified for the failures.
-     */
+    // TODO: (In Android S or later) if we add callback methods for generic failures
+    //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
     private static final long DUMMY_REQUEST_ID = -1;
 
     private final Context mContext;
@@ -1150,35 +1146,24 @@
 
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
             int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
-            MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
+            MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
             MediaRoute2ProviderInfo prevInfo =
                     (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
+            if (Objects.equals(prevInfo, currentInfo)) return;
 
-            if (Objects.equals(prevInfo, providerInfo)) return;
-
+            List<MediaRoute2Info> addedRoutes = new ArrayList<>();
+            List<MediaRoute2Info> removedRoutes = new ArrayList<>();
+            List<MediaRoute2Info> changedRoutes = new ArrayList<>();
             if (prevInfo == null) {
-                mLastProviderInfos.add(providerInfo);
-                Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes();
-                if (addedRoutes.size() > 0) {
-                    sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToRouters,
-                            this, getRouters(), new ArrayList<>(addedRoutes)));
-                }
-            } else if (providerInfo == null) {
+                mLastProviderInfos.add(currentInfo);
+                addedRoutes.addAll(currentInfo.getRoutes());
+            } else if (currentInfo == null) {
                 mLastProviderInfos.remove(prevInfo);
-                Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes();
-                if (removedRoutes.size() > 0) {
-                    sendMessage(PooledLambda.obtainMessage(
-                            UserHandler::notifyRoutesRemovedToRouters,
-                            this, getRouters(), new ArrayList<>(removedRoutes)));
-                }
+                removedRoutes.addAll(prevInfo.getRoutes());
             } else {
-                mLastProviderInfos.set(providerInfoIndex, providerInfo);
-                List<MediaRoute2Info> addedRoutes = new ArrayList<>();
-                List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-                List<MediaRoute2Info> changedRoutes = new ArrayList<>();
-
-                final Collection<MediaRoute2Info> currentRoutes = providerInfo.getRoutes();
-                final Set<String> updatedRouteIds = new HashSet<>();
+                mLastProviderInfos.set(providerInfoIndex, currentInfo);
+                final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
+                final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
 
                 for (MediaRoute2Info route : currentRoutes) {
                     if (!route.isValid()) {
@@ -1186,37 +1171,33 @@
                         continue;
                     }
                     MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
-
-                    if (prevRoute != null) {
-                        if (!Objects.equals(prevRoute, route)) {
-                            changedRoutes.add(route);
-                        }
-                        updatedRouteIds.add(route.getId());
-                    } else {
+                    if (prevRoute == null) {
                         addedRoutes.add(route);
+                    } else if (!Objects.equals(prevRoute, route)) {
+                        changedRoutes.add(route);
                     }
                 }
 
                 for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
-                    if (!updatedRouteIds.contains(prevRoute.getId())) {
+                    if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
                         removedRoutes.add(prevRoute);
                     }
                 }
+            }
 
-                List<IMediaRouter2> routers = getRouters();
-                List<IMediaRouter2Manager> managers = getManagers();
-                if (addedRoutes.size() > 0) {
-                    notifyRoutesAddedToRouters(routers, addedRoutes);
-                    notifyRoutesAddedToManagers(managers, addedRoutes);
-                }
-                if (removedRoutes.size() > 0) {
-                    notifyRoutesRemovedToRouters(routers, removedRoutes);
-                    notifyRoutesRemovedToManagers(managers, removedRoutes);
-                }
-                if (changedRoutes.size() > 0) {
-                    notifyRoutesChangedToRouters(routers, changedRoutes);
-                    notifyRoutesChangedToManagers(managers, changedRoutes);
-                }
+            List<IMediaRouter2> routers = getRouters();
+            List<IMediaRouter2Manager> managers = getManagers();
+            if (addedRoutes.size() > 0) {
+                notifyRoutesAddedToRouters(routers, addedRoutes);
+                notifyRoutesAddedToManagers(managers, addedRoutes);
+            }
+            if (removedRoutes.size() > 0) {
+                notifyRoutesRemovedToRouters(routers, removedRoutes);
+                notifyRoutesRemovedToManagers(managers, removedRoutes);
+            }
+            if (changedRoutes.size() > 0) {
+                notifyRoutesChangedToRouters(routers, changedRoutes);
+                notifyRoutesChangedToManagers(managers, changedRoutes);
             }
         }
 
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 2ed6e16..bfc76df 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -250,26 +250,36 @@
 
             for (int i = mHistoryFiles.size() - 1; i >= 0; i--) {
                 final AtomicFile currentOldestFile = mHistoryFiles.get(i);
-                final long creationTime = Long.parseLong(currentOldestFile.getBaseFile().getName());
-                if (DEBUG) {
-                    Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName()
-                            + " created on " + creationTime);
-                }
-                if (creationTime <= retentionBoundary.getTimeInMillis()) {
+                try {
+                    final long creationTime = Long.parseLong(
+                            currentOldestFile.getBaseFile().getName());
                     if (DEBUG) {
-                        Slog.d(TAG, "Removed " + currentOldestFile.getBaseFile().getName());
+                        Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName()
+                                + " created on " + creationTime);
                     }
-                    currentOldestFile.delete();
-                    // TODO: delete all relevant bitmaps, once they exist
-                    mHistoryFiles.removeLast();
-                } else {
-                    // all remaining files are newer than the cut off; schedule jobs to delete
-                    scheduleDeletion(currentOldestFile.getBaseFile(), creationTime, retentionDays);
+                    if (creationTime <= retentionBoundary.getTimeInMillis()) {
+                        deleteFile(currentOldestFile);
+                    } else {
+                        // all remaining files are newer than the cut off; schedule jobs to delete
+                        scheduleDeletion(
+                                currentOldestFile.getBaseFile(), creationTime, retentionDays);
+                    }
+                } catch (NumberFormatException e) {
+                    deleteFile(currentOldestFile);
                 }
             }
         }
     }
 
+    private void deleteFile(AtomicFile file) {
+        if (DEBUG) {
+            Slog.d(TAG, "Removed " + file.getBaseFile().getName());
+        }
+        file.delete();
+        // TODO: delete all relevant bitmaps, once they exist
+        mHistoryFiles.removeLast();
+    }
+
     private void scheduleDeletion(File file, long creationTime, int retentionDays) {
         final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS);
         scheduleDeletion(file, deletionTime);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4aeddc8..e8d8ed7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -107,7 +107,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -155,6 +154,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -1730,6 +1730,11 @@
     }
 
     @VisibleForTesting
+    void setShortcutHelper(ShortcutHelper helper) {
+        mShortcutHelper = helper;
+    }
+
+    @VisibleForTesting
     void setHints(int hints) {
         mListenerHints = hints;
     }
@@ -3459,10 +3464,14 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(onlyImportant);
             for (ConversationChannelWrapper conversation : conversations) {
-                conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
-                        conversation.getNotificationChannel().getConversationId(),
-                        conversation.getPkg(),
-                        UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
+                if (mShortcutHelper == null) {
+                    conversation.setShortcutInfo(null);
+                } else {
+                    conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
+                            conversation.getNotificationChannel().getConversationId(),
+                            conversation.getPkg(),
+                            UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
+                }
             }
             return new ParceledListSlice<>(conversations);
         }
@@ -3482,10 +3491,14 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(pkg, uid);
             for (ConversationChannelWrapper conversation : conversations) {
-                conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
-                        conversation.getNotificationChannel().getConversationId(),
-                        pkg,
-                        UserHandle.of(UserHandle.getUserId(uid))));
+                if (mShortcutHelper == null) {
+                    conversation.setShortcutInfo(null);
+                } else {
+                    conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
+                            conversation.getNotificationChannel().getConversationId(),
+                            pkg,
+                            UserHandle.of(UserHandle.getUserId(uid))));
+                }
             }
             return new ParceledListSlice<>(conversations);
         }
@@ -5680,8 +5693,10 @@
             }
         }
 
-        r.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
-                notification.getShortcutId(), pkg, user));
+        ShortcutInfo info = mShortcutHelper != null
+                ? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user)
+                : null;
+        r.setShortcutInfo(info);
 
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.getSbn().getOverrideGroupKey() != null)) {
@@ -6214,8 +6229,11 @@
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker);
                     updateLightsLocked();
-                    mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, true /* isRemoved */,
-                            mHandler);
+                    if (mShortcutHelper != null) {
+                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+                                true /* isRemoved */,
+                                mHandler);
+                    }
                 } else {
                     // No notification was found, assume that it is snoozed and cancel it.
                     if (mReason != REASON_SNOOZED) {
@@ -6453,9 +6471,11 @@
                                 + n.getPackageName());
                     }
 
-                    mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
-                            false /* isRemoved */,
-                            mHandler);
+                    if (mShortcutHelper != null) {
+                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+                                false /* isRemoved */,
+                                mHandler);
+                    }
 
                     maybeRecordInterruptionLocked(r);
 
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 7eb3f01..d89605a 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -542,12 +542,6 @@
         void unregisterPointerEventListener(PointerEventListener listener, int displayId);
 
         /**
-         * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
-         * {@param activityType}.
-         */
-        void getStackBounds(int windowingMode, int activityType, Rect outBounds);
-
-        /**
          * @return The currently active input method window.
          */
         WindowState getInputMethodWindowLw();
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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 682e991..7803c73 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2802,6 +2802,9 @@
                     false /* includingParents */);
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
+        // Clear out current windowing mode before reparenting to split taks.
+        wct.setWindowingMode(
+                task.getStack().mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_UNDEFINED);
         wct.reparent(task.getStack().mRemoteToken.toWindowContainerToken(),
                 primarySplitTask.mRemoteToken.toWindowContainerToken(), toTop);
         mWindowOrganizerController.applyTransaction(wct);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ce7e797..55b7be779 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -197,6 +197,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.ITaskOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -822,8 +823,10 @@
 
             if (w.mHasSurface && isDisplayed) {
                 final int type = w.mAttrs.type;
-                if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR
-                        || mWmService.mPolicy.isKeyguardShowing()) {
+                if (type == TYPE_SYSTEM_DIALOG
+                        || type == TYPE_SYSTEM_ERROR
+                        || (type == TYPE_NOTIFICATION_SHADE
+                            &&  mWmService.mPolicy.isKeyguardShowing())) {
                     mTmpApplySurfaceChangesTransactionState.syswin = true;
                 }
                 if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 367151c..221258e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -3246,9 +3246,14 @@
                 mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
         final int dockedAppearance = updateLightStatusBarAppearanceLw(0 /* vis */,
                 mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
-        mService.getStackBounds(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
-        final boolean inSplitScreen = !mDockedStackBounds.isEmpty();
+        final boolean inSplitScreen =
+                mService.mRoot.getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated();
+        if (inSplitScreen) {
+            mService.getStackBounds(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+                    mDockedStackBounds);
+        } else {
+            mDockedStackBounds.setEmpty();
+        }
         mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         : WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0151b82..7c6343c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4068,21 +4068,7 @@
 
     @Override
     boolean isOrganized() {
-        final Task rootTask = getRootTask();
-        if (rootTask.mTaskOrganizer == null) {
-            // You are obviously not organized...
-            return false;
-        }
-        if (rootTask == this) {
-            // Root tasks can be organized.
-            return true;
-        }
-        if (rootTask.mCreatedByOrganizer && getParent() == rootTask) {
-            // Direct children of tasks added by the organizer can the organized.
-            return true;
-        }
-
-        return false;
+        return mTaskOrganizer != null;
     }
 
     @Override
@@ -4137,6 +4123,7 @@
         }
    }
 
+    @VisibleForTesting
     boolean setTaskOrganizer(ITaskOrganizer organizer) {
         if (mTaskOrganizer == organizer) {
             return false;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 9356ec2..77ef011 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -732,11 +732,11 @@
     /**
      * Returns an existing stack compatible with the windowing mode and activity type or creates one
      * if a compatible stack doesn't exist.
-     * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean)
+     * @see #getOrCreateStack(int, int, boolean, Intent, Task)
      */
     ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) {
         return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
-                null /* candidateTask */, false /* createdByOrganizer */);
+                null /* candidateTask */);
     }
 
     /**
@@ -748,7 +748,7 @@
      * @see #createStack(int, int, boolean)
      */
     ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop,
-            Intent intent, Task candidateTask, boolean createdByOrganizer) {
+            Intent intent, Task candidateTask) {
         if (!alwaysCreateStack(windowingMode, activityType)) {
             ActivityStack stack = getStack(windowingMode, activityType);
             if (stack != null) {
@@ -757,13 +757,13 @@
         } else if (candidateTask != null) {
             final ActivityStack stack = (ActivityStack) candidateTask;
             final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
-            if (isSplitScreenModeActivated()) {
-                final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
-                        && t.inSplitScreenSecondaryWindowingMode());
+            Task launchRootTask = updateLaunchRootTask(windowingMode);
+
+            if (launchRootTask != null) {
                 if (stack.getParent() == null) {
-                    splitRootSecondary.addChild(stack, position);
-                } else if (stack.getParent() != splitRootSecondary) {
-                    stack.reparent(splitRootSecondary, position);
+                    launchRootTask.addChild(stack, position);
+                } else if (stack.getParent() != launchRootTask) {
+                    stack.reparent(launchRootTask, position);
                 }
             } else if (stack.getDisplay() != mDisplayContent || !stack.isRootTask()) {
                 if (stack.getParent() == null) {
@@ -779,7 +779,7 @@
             return stack;
         }
         return createStack(windowingMode, activityType, onTop, null /*info*/, intent,
-                createdByOrganizer);
+                false /* createdByOrganizer */);
     }
 
     /**
@@ -798,7 +798,7 @@
         // it's display's windowing mode.
         windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
         return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
-                candidateTask, false /* createdByOrganizer */);
+                candidateTask);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 872f254..b641e4c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -277,9 +277,9 @@
                     return null;
                 }
 
-                final Task task = display.mTaskContainers.getOrCreateStack(windowingMode,
-                        ACTIVITY_TYPE_UNDEFINED, false /* onTop */, new Intent(),
-                        null /* candidateTask */, true /* createdByOrganizer */);
+                final Task task = display.mTaskContainers.createStack(windowingMode,
+                        ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
+                        true /* createdByOrganizer */);
                 RunningTaskInfo out = task.getTaskInfo();
                 mLastSentTaskInfos.put(task, out);
                 return out;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 29a2e18..e43f4b4 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -304,7 +304,11 @@
         }
     }
 
-    boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
+    boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
+        final DisplayInfo displayInfo = wallpaperWin.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
         int xOffset = 0;
         int yOffset = 0;
         boolean rawChanged = false;
@@ -444,10 +448,6 @@
     }
 
     private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
-        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
-        final int dw = displayInfo.logicalWidth;
-        final int dh = displayInfo.logicalHeight;
-
         WindowState target = mWallpaperTarget;
         if (target != null) {
             if (target.mWallpaperX >= 0) {
@@ -484,7 +484,7 @@
         }
 
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
-            mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(dw, dh, sync);
+            mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index e29580b..203ca25 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -73,11 +73,11 @@
         }
     }
 
-    void updateWallpaperOffset(int dw, int dh, boolean sync) {
+    void updateWallpaperOffset(boolean sync) {
         final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
-            if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
+            if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) {
                 // We only want to be synchronous with one wallpaper.
                 sync = false;
             }
@@ -85,10 +85,6 @@
     }
 
     void updateWallpaperVisibility(boolean visible) {
-        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
-        final int dw = displayInfo.logicalWidth;
-        final int dh = displayInfo.logicalHeight;
-
         if (isVisible() != visible) {
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
@@ -98,7 +94,7 @@
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
             if (visible) {
-                wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+                wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
             }
 
             wallpaper.dispatchWallpaperVisibility(visible);
@@ -145,19 +141,11 @@
             }
         }
 
-        DisplayInfo displayInfo = getFixedRotationTransformDisplayInfo();
-        if (displayInfo == null) {
-            displayInfo = mDisplayContent.getDisplayInfo();
-        }
-
-        final int dw = displayInfo.logicalWidth;
-        final int dh = displayInfo.logicalHeight;
-
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
 
             if (visible) {
-                wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+                wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
             }
 
             // First, make sure the client has the current visibility state.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8e45752..dfaa0ec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2332,9 +2332,7 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
             if (toBeDisplayed && win.mIsWallpaper) {
-                DisplayInfo displayInfo = displayContent.getDisplayInfo();
-                displayContent.mWallpaperController.updateWallpaperOffset(
-                        win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+                displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */);
             }
             if (win.mActivityRecord != null) {
                 win.mActivityRecord.updateReportedVisibilityLocked();
@@ -2782,7 +2780,6 @@
                 aspectRatio);
     }
 
-    @Override
     public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
         synchronized (mGlobalLock) {
             final ActivityStack stack = mRoot.getStack(windowingMode, activityType);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7dcf375..b87d181 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1101,7 +1101,6 @@
                 }
             }
 
-            final ActivityStack stack = getRootTask();
             layoutDisplayFrame = new Rect(windowFrames.mDisplayFrame);
             windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame);
             layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left;
@@ -1205,8 +1204,7 @@
 
         if (mIsWallpaper && (fw != windowFrames.mFrame.width()
                 || fh != windowFrames.mFrame.height())) {
-            dc.mWallpaperController.updateWallpaperOffset(this,
-                    displayInfo.logicalWidth, displayInfo.logicalHeight, false /* sync */);
+            dc.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
         }
 
         // Calculate relative frame
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 563710b..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) {
@@ -1381,7 +1389,8 @@
             return true;
         }
 
-        if (isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+        final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
+        if (isEntrance && isImeWindow) {
             mWin.getDisplayContent().adjustForImeIfNeeded();
             mWin.setDisplayLayoutNeeded();
             mService.mWindowPlacerLocked.requestTraversal();
@@ -1435,11 +1444,11 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                 mAnimationIsEntrance = isEntrance;
             }
-        } else {
+        } else if (!isImeWindow) {
             mWin.cancelAnimation();
         }
 
-        if (!isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+        if (!isEntrance && isImeWindow) {
             mWin.getDisplayContent().adjustForImeIfNeeded();
         }
 
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1544ff1..eed39e1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9119,6 +9119,31 @@
     }
 
     @Override
+    public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+            @NonNull UserHandle userHandle) {
+        if (!mHasFeature) {
+            return null;
+        }
+        synchronized (getLockObject()) {
+            final String supervisor = mContext.getResources().getString(
+                    com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
+            if (supervisor == null) {
+                return null;
+            }
+            final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+            final ComponentName doComponent = mOwners.getDeviceOwnerComponent();
+            final ComponentName poComponent =
+                    mOwners.getProfileOwnerComponent(userHandle.getIdentifier());
+            if (supervisorComponent.equals(doComponent) || supervisorComponent.equals(
+                    poComponent)) {
+                return supervisorComponent;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    @Override
     public String getProfileOwnerName(int userHandle) {
         if (!mHasFeature) {
             return null;
@@ -11488,6 +11513,18 @@
             throw new SecurityException(
                     "User " + userId + " is not allowed to call setSecondaryLockscreenEnabled");
         }
+        // Only the default supervision app can use this API.
+        final String supervisor = mContext.getResources().getString(
+                com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
+        if (supervisor == null) {
+            throw new SecurityException("Unable to set secondary lockscreen setting, no "
+                    + "default supervision component defined");
+        }
+        final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+        if (!who.equals(supervisorComponent)) {
+            throw new SecurityException(
+                    "Admin " + who + " is not the default supervision component");
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1939313..2a914ec 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -30,6 +30,7 @@
 import android.app.ActivityThread;
 import android.app.AppCompatCallbacks;
 import android.app.INotificationManager;
+import android.app.SystemServiceRegistry;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -513,6 +514,8 @@
             Looper.getMainLooper().setSlowLogThresholdMs(
                     SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
 
+            SystemServiceRegistry.sEnableServiceNotFoundWtf = true;
+
             // Initialize native services.
             System.loadLibrary("android_servers");
 
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index 09e333e..db464e7 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -21,6 +21,7 @@
 import android.net.NattKeepalivePacketData;
 import android.net.ProxyInfo;
 import android.net.TcpKeepalivePacketData;
+import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.util.KeepalivePacketDataUtil;
 import android.os.Binder;
@@ -292,4 +293,20 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+
+    /**
+     * Update the bssid, L2 key and group hint layer2 information.
+     */
+    public boolean updateLayer2Information(Layer2Information info) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.updateLayer2Information(info.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error updating layer2 information", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 }
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 136ee91..c87ece2 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -220,7 +220,7 @@
         String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
         @Event.EventType int eventType = mimeTypeToShareEventType(mimeType);
         EventHistoryImpl eventHistory;
-        if (ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE.equals(event.getLaunchLocation())) {
+        if (ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE.equals(event.getLaunchLocation())) {
             // Direct share event
             if (appTarget.getShortcutInfo() == null) {
                 return;
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 5c82200..736a7be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -79,7 +79,8 @@
     private static final String CALLING_PACKAGE2 = "com.package.name2";
     private static final String NAMESPACE1 = "namespace1";
     private static final String NAMESPACE2 = "namespace2";
-    private static final String DISABLE_RESCUE_PARTY_FLAG = "disable_rescue_party";
+    private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+            "persist.device_config.configuration.disable_rescue_party";
 
     private MockitoSession mSession;
     private HashMap<String, String> mSystemSettingsMap;
@@ -172,6 +173,7 @@
                 Integer.toString(RescueParty.LEVEL_NONE));
         SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0));
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+        SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
     }
 
     @After
@@ -317,13 +319,6 @@
 
     @Test
     public void testExplicitlyEnablingAndDisablingRescue() {
-        // mock the DeviceConfig get call to avoid hitting
-        // android.permission.READ_DEVICE_CONFIG when calling real DeviceConfig.
-        doReturn(true)
-                .when(() -> DeviceConfig.getBoolean(
-                    eq(DeviceConfig.NAMESPACE_CONFIGURATION),
-                    eq(DISABLE_RESCUE_PARTY_FLAG),
-                    eq(false)));
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
         SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
@@ -336,18 +331,15 @@
 
     @Test
     public void testDisablingRescueByDeviceConfigFlag() {
-        doReturn(true)
-                .when(() -> DeviceConfig.getBoolean(
-                    eq(DeviceConfig.NAMESPACE_CONFIGURATION),
-                    eq(DISABLE_RESCUE_PARTY_FLAG),
-                    eq(false)));
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+        SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
 
         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
 
         // Restore the property value initalized in SetUp()
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+        SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index baf551e..fe47cea 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4274,6 +4274,9 @@
 
         // Profile owner can set enabled state.
         setAsProfileOwner(admin1);
+        when(mServiceContext.resources
+                .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+                .thenReturn(admin1.flattenToString());
         dpm.setSecondaryLockscreenEnabled(admin1, true);
         assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
                 DpmMockContext.CALLER_USER_HANDLE)));
@@ -4297,6 +4300,9 @@
 
         // Device owners can set enabled state.
         setupDeviceOwner();
+        when(mServiceContext.resources
+                .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+                .thenReturn(admin1.flattenToString());
         dpm.setSecondaryLockscreenEnabled(admin1, true);
         assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of(UserHandle.USER_SYSTEM)));
     }
@@ -4309,12 +4315,39 @@
                 DpmMockContext.CALLER_USER_HANDLE)));
 
         // Non-DO/PO cannot set enabled state.
+        when(mServiceContext.resources
+                .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+                .thenReturn(admin1.flattenToString());
         assertExpectException(SecurityException.class, /* messageRegex= */ null,
                 () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
         assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
                 DpmMockContext.CALLER_USER_HANDLE)));
     }
 
+    public void testSecondaryLockscreen_nonSupervisionApp() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+        // Initial state is disabled.
+        assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+                DpmMockContext.CALLER_USER_HANDLE)));
+
+        // Caller is Profile Owner, but no supervision app is configured.
+        setAsProfileOwner(admin1);
+        assertExpectException(SecurityException.class, "no default supervision component defined",
+                () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
+        assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+                DpmMockContext.CALLER_USER_HANDLE)));
+
+        // Caller is Profile Owner, but is not the default configured supervision app.
+        when(mServiceContext.resources
+                .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+                .thenReturn(admin2.flattenToString());
+        assertExpectException(SecurityException.class, "is not the default supervision component",
+                () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
+        assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+                DpmMockContext.CALLER_USER_HANDLE)));
+    }
+
     public void testIsDeviceManaged() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
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/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 5199604..728e149 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -299,7 +299,7 @@
                 .build();
         AppTargetEvent appTargetEvent =
                 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
-                        .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+                        .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
                         .build();
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
         mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
@@ -319,7 +319,7 @@
                 .build();
         AppTargetEvent appTargetEvent =
                 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
-                        .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+                        .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
                         .build();
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
 
@@ -667,7 +667,7 @@
                 .build();
         AppTargetEvent appTargetEvent =
                 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
-                        .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+                        .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
                         .build();
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
index 191c038..5412bb5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
@@ -18,7 +18,9 @@
 
 import android.content.pm.PackageManager
 import android.platform.test.annotations.Presubmit
+import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
 import org.junit.Test
 
 /**
@@ -28,6 +30,9 @@
 @Presubmit
 class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
 
+    @get:Rule
+    val expect = Expect.create()
+
     @Test
     fun applicationInfoEquality() {
         val flags = PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES
@@ -41,7 +46,8 @@
             } else {
                 "$firstName | $secondName"
             }
-            assertWithMessage(packageName).that(it.first?.dumpToString())
+            expect.withMessage("${it.first?.sourceDir} $packageName")
+                    .that(it.first?.dumpToString())
                     .isEqualTo(it.second?.dumpToString())
         }
     }
@@ -71,7 +77,8 @@
             } else {
                 "$firstName | $secondName"
             }
-            assertWithMessage(packageName).that(it.first?.dumpToString())
+            expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName")
+                    .that(it.first?.dumpToString())
                     .isEqualTo(it.second?.dumpToString())
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index 19bf9b6..7b1b2d2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -29,14 +29,17 @@
 import android.content.pm.ProviderInfo
 import android.os.Debug
 import android.os.Environment
+import android.os.ServiceManager
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.compat.IPlatformCompat
 import com.android.server.pm.PackageManagerService
 import com.android.server.pm.PackageSetting
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateUnserialized
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
+import org.junit.After
 import org.junit.BeforeClass
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
@@ -59,7 +62,27 @@
             setCallback { false /* hasFeature */ }
         }
 
-        protected val packageParser2 = TestPackageParser2()
+        private val platformCompat = IPlatformCompat.Stub
+                .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE))
+
+        protected val packageParser2 = PackageParser2(null /* separateProcesses */,
+                false /* onlyCoreApps */, context.resources.displayMetrics, null /* cacheDir */,
+                object : PackageParser2.Callback() {
+                    override fun isChangeEnabled(
+                        changeId: Long,
+                        appInfo: ApplicationInfo
+                    ): Boolean {
+                        // This test queries PlatformCompat because prebuilts in the tree
+                        // may not be updated to be compliant with the latest enforcement checks.
+                        return platformCompat.isChangeEnabled(changeId, appInfo)
+                    }
+
+                    // Assume the device doesn't support anything. This will affect permission
+                    // parsing and will force <uses-permission/> declarations to include all
+                    // requiredNotFeature permissions and exclude all requiredFeature permissions.
+                    // This mirrors the old behavior.
+                    override fun hasFeature(feature: String) = false
+                })
 
         /**
          * It would be difficult to mock all possibilities, so just use the APKs on device.
@@ -91,35 +114,31 @@
 
         lateinit var newPackages: List<AndroidPackage>
 
-        var failureInBeforeClass: Throwable? = null
+        private val thrownInSetUp = mutableListOf<Throwable>()
 
         @Suppress("ConstantConditionIf")
         @JvmStatic
         @BeforeClass
         fun setUpPackages() {
-            failureInBeforeClass = null
-            try {
-                this.oldPackages = apks.map {
+            this.oldPackages = apks.mapNotNull {
+                tryOrNull {
                     packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
                 }
+            }
 
-                this.newPackages = apks.map {
+            this.newPackages = apks.mapNotNull {
+                tryOrNull {
                     packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
                 }
+            }
 
-                if (DUMP_HPROF_TO_EXTERNAL) {
-                    System.gc()
-                    Environment.getExternalStorageDirectory()
-                            .resolve(
-                                    "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
-                            .absolutePath
-                            .run(Debug::dumpHprofData)
-                }
-            } catch (t: Throwable) {
-                // If we crash here we cause a tool failure (because we don't run any of the tests
-                // in the subclasses, leading to a difference between expected and actual test
-                // result counts).
-                failureInBeforeClass = t
+            if (DUMP_HPROF_TO_EXTERNAL) {
+                System.gc()
+                Environment.getExternalStorageDirectory()
+                        .resolve(
+                                "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
+                        .absolutePath
+                        .run(Debug::dumpHprofData)
             }
         }
 
@@ -146,13 +165,36 @@
             this.pkg = aPkg
             whenever(pkgState) { PackageStateUnserialized() }
         }
+
+        private fun <T> tryOrNull(block: () -> T) = try {
+            block()
+        } catch (t: Throwable) {
+            thrownInSetUp.add(t)
+            null
+        }
     }
 
-    @org.junit.Before
+    @After
     fun verifySetUpPackages() {
-        failureInBeforeClass?.let {
-            throw AssertionError("setUpPackages failed:", it)
-        }
+        if (thrownInSetUp.isEmpty()) return
+        val exception = AssertionError("setUpPackages failed with ${thrownInSetUp.size} errors:\n" +
+                thrownInSetUp.joinToString(separator = "\n") { it.message.orEmpty() })
+
+        /*
+            Testing infrastructure doesn't currently support errors thrown in @AfterClass,
+            so instead it's thrown here. But to avoid throwing a massive repeated stack for every
+            test method, only throw on the first method run in the class, clearing the list so that
+            subsequent methods can run without failing. Doing this in @After lets true method
+            failures propagate, as those should throw before this does.
+
+            This will cause the failure to be attached to a different method depending on run order,
+            which could make comparisons difficult. So if a failure points here, it's worth
+            checking failures for all methods in all subclasses.
+
+            TODO: When infrastructure supports @AfterClass errors, move this
+        */
+        thrownInSetUp.clear()
+        throw exception
     }
 
     // The following methods dump an exact set of fields from the object to compare, because
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 3991d8d..80b474f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -146,7 +146,29 @@
         assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
 
         verify(mAlarmManager, times(6)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
+    }
 
+    @Test
+    public void testPrune_badFileName() {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.setTimeInMillis(10);
+        int retainDays = 1;
+
+        List<AtomicFile> expectedFiles = new ArrayList<>();
+
+        // add 5 files with a creation date of "today", but the file names are bad
+        for (long i = cal.getTimeInMillis(); i >= 5; i--) {
+            File file = mock(File.class);
+            when(file.getName()).thenReturn(i + ".txt");
+            AtomicFile af = new AtomicFile(file);
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+
+        // trim everything a day+ old
+        cal.add(Calendar.DATE, 1 * retainDays);
+        mDataBase.prune(retainDays, cal.getTimeInMillis());
+
+        assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f083f0e..f9596b5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6504,4 +6504,19 @@
         assertNull(conversations.get(0).getShortcutInfo());
         assertNull(conversations.get(1).getShortcutInfo());
     }
+
+    @Test
+    public void testShortcutHelperNull_doesntCrashEnqueue() throws RemoteException {
+        mService.setShortcutHelper(null);
+        NotificationRecord nr =
+                generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                        "testShortcutHelperNull_doesntCrashEnqueue");
+        try {
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                    nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+            waitForIdle();
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 551e186..5a527a2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -3,8 +3,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
+import android.app.Application;
 import android.content.Intent;
 import android.net.Uri;
 import android.service.notification.Condition;
@@ -45,7 +47,7 @@
                 null,               // ActivityThread not actually used in Service
                 ScheduleConditionProvider.class.getName(),
                 null,               // token not needed when not talking with the activity manager
-                null,
+                mock(Application.class),
                 null                // mocked services don't talk with the activity manager
                 );
         service.onCreate();
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 9cfee34..38b3d76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1039,6 +1039,13 @@
         assertEquals(config90.orientation, app.getConfiguration().orientation);
         assertEquals(config90.windowConfiguration.getBounds(), app.getBounds());
 
+        // Force the negative offset to verify it can be updated.
+        mWallpaperWindow.mWinAnimator.mXOffset = mWallpaperWindow.mWinAnimator.mYOffset = -1;
+        assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow,
+                false /* sync */));
+        assertThat(mWallpaperWindow.mWinAnimator.mXOffset).isGreaterThan(-1);
+        assertThat(mWallpaperWindow.mWinAnimator.mYOffset).isGreaterThan(-1);
+
         mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
 
         // The animation in old rotation should be cancelled.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 78e5f2d..327e8b3 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -52,6 +52,14 @@
     public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
 
     /**
+     * Extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate whether this is a
+     * rebroadcast on unlock. Defaults to {@code false} if not specified.
+     * @hide
+     */
+    public static final String EXTRA_REBROADCAST_ON_UNLOCK =
+            "android.telephony.extra.REBROADCAST_ON_UNLOCK";
+
+    /**
      * Optional extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the
      * subscription index that the broadcast is for, if a valid one is available.
      */
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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4e5be5c..d6cdaa6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11776,6 +11776,7 @@
      * subscription, the key is {@link SubscriptionManager#getDefaultSubscriptionId}) and the value
      * as the list of {@link EmergencyNumber}; empty Map if this information is not available;
      * or throw a SecurityException if the caller does not have the permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     @NonNull
@@ -11823,6 +11824,7 @@
      * @param number - the number to look up
      * @return {@code true} if the given number is an emergency number based on current locale,
      * SIM card(s), Android database, modem, network or defaults; {@code false} otherwise.
+     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     public boolean isEmergencyNumber(@NonNull String number) {
         try {
@@ -11858,7 +11860,7 @@
      * the same digits of any current emergency number based on current locale, sim, modem and
      * network; {@code false} if it is not; or throw an SecurityException if the caller does not
      * have the required permission/privileges
-     *
+     * @throws IllegalStateException if the Telephony process is not currently available.
      * @hide
      */
     @SystemApi
@@ -12344,6 +12346,9 @@
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     @IsMultiSimSupportedResult
     public int isMultiSimSupported() {
+        if (getSupportedModemCount() < 2) {
+            return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
+        }
         try {
             ITelephony service = getITelephony();
             if (service != null) {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index a616c61..6c9ffe2 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -477,4 +477,12 @@
         StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
         assertThat(sm.isCheckpointSupported()).isTrue();
     }
+
+    @Test
+    public void hasMainlineModule() throws Exception {
+        String pkgName = getModuleMetadataPackageName();
+        boolean existed =  InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager().getModuleInfo(pkgName, 0) != null;
+        assertThat(existed).isTrue();
+    }
 }
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 282f012..78775be 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -243,6 +243,7 @@
      */
     @Test
     public void testRollbackWhitelistedApp() throws Exception {
+        assumeTrue(hasMainlineModule());
         runPhase("testRollbackWhitelistedApp_Phase1");
         getDevice().reboot();
         runPhase("testRollbackWhitelistedApp_Phase2");
@@ -460,4 +461,16 @@
             return false;
         }
     }
+
+    /**
+     * True if this build has mainline modules installed.
+     */
+    private boolean hasMainlineModule() throws Exception {
+        try {
+            runPhase("hasMainlineModule");
+            return true;
+        } catch (AssertionError ignore) {
+            return false;
+        }
+    }
 }
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index b4f0daa..8de27e8 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -445,14 +445,20 @@
         // Check comparisons work.
         LinkProperties lp2 = new LinkProperties(lp);
         assertAllRoutesHaveInterface("wlan0", lp2);
-        assertEquals(0, lp.compareAllRoutes(lp2).added.size());
-        assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+        // LinkProperties#compareAllRoutes exists both in R and before R, but the return type
+        // changed in R, so a test compiled with the R version of LinkProperties cannot run on Q.
+        if (isAtLeastR()) {
+            assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+            assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+        }
 
         lp2.setInterfaceName("p2p0");
         assertAllRoutesHaveInterface("p2p0", lp2);
         assertAllRoutesNotHaveInterface("wlan0", lp2);
-        assertEquals(3, lp.compareAllRoutes(lp2).added.size());
-        assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+        if (isAtLeastR()) {
+            assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+            assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+        }
 
         // Remove route with incorrect interface, no route removed.
         lp.removeRoute(new RouteInfo(prefix2, null, null));
@@ -946,7 +952,7 @@
 
     }
 
-    @Test
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testCompareResult() {
         // Either adding or removing items
         compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
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.