Merge "Change AtomicFile to use rename-into-place." into rvc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index e8afa1d..f511af5 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -92,6 +92,9 @@
         sdk_version: "module_current",
     },
 
+    // Configure framework module specific metalava options.
+    droiddoc_options: [mainline_stubs_args],
+
     // The stub libraries must be visible to frameworks/base so they can be combined
     // into API specific libraries.
     stubs_library_visibility: [
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 2834ab1..94e5d0b 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1098,8 +1098,7 @@
 
             if (mAppWidgetManager != null
                     && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
-                // TODO: consider lowering to ACTIVE
-                return STANDBY_BUCKET_EXEMPTED;
+                return STANDBY_BUCKET_ACTIVE;
             }
 
             if (isDeviceProvisioningPackage(packageName)) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f00a35d..8ba52ef 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -440,6 +440,7 @@
             273 [(module) = "permissioncontroller"];
         EvsUsageStatsReported evs_usage_stats_reported = 274 [(module) = "evs"];
         AudioPowerUsageDataReported audio_power_usage_data_reported = 275;
+        TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"];
         SdkExtensionStatus sdk_extension_status = 354;
 
         // StatsdStats tracks platform atoms with ids upto 500.
@@ -447,7 +448,7 @@
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10082
+    // Next: 10084
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"];
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"];
@@ -539,6 +540,9 @@
         SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"];
         SettingSnapshot setting_snapshot = 10080 [(module) = "framework"];
         DisplayWakeReason display_wake_reason = 10081 [(module) = "framework"];
+        DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"];
+        BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered =
+                10083 [(module) = "framework"];
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -4940,6 +4944,52 @@
 }
 
 /**
+ * Used for pull network statistics via mobile|wifi networks, and sliced by interesting dimensions.
+ * Note that data is expected to be sliced into more dimensions in future. In other words,
+ * the caller must not assume the data is unique when filtering with a set of matching conditions.
+ * Thus, as the dimension grows, the caller will not be affected.
+ *
+ * Pulled from:
+ *   StatsPullAtomService (using NetworkStatsService to get NetworkStats)
+ */
+message DataUsageBytesTransfer {
+    // State of this record. Should be NetworkStats#SET_DEFAULT or NetworkStats#SET_FOREGROUND to
+    // indicate the foreground state, or NetworkStats#SET_ALL to indicate the record is for all
+    // states combined, not including debug states. See NetworkStats#SET_*.
+    optional int32 state = 1;
+
+    optional int64 rx_bytes = 2;
+
+    optional int64 rx_packets = 3;
+
+    optional int64 tx_bytes = 4;
+
+    optional int64 tx_packets = 5;
+
+    // Radio Access Technology (RAT) type of this record, should be one of
+    // TelephonyManager#NETWORK_TYPE_* constants, or NetworkTemplate#NETWORK_TYPE_ALL to indicate
+    // the record is for all rat types combined.
+    optional int32 rat_type = 6;
+
+    // Mcc/Mnc read from sim if the record is for a specific subscription, null indicates the
+    // record is combined regardless of subscription.
+    optional string sim_mcc = 7;
+    optional string sim_mnc = 8;
+
+    // Enumeration of opportunistic states with an additional ALL state indicates the record is
+    // combined regardless of the boolean value in its field.
+    enum DataSubscriptionState {
+        ALL = 1;
+        OPPORTUNISTIC = 2;
+        NOT_OPPORTUNISTIC = 3;
+    }
+    // Mark whether the subscription is an opportunistic data subscription, and ALL indicates the
+    // record is combined regardless of opportunistic data subscription.
+    // See {@link SubscriptionManager#setOpportunistic}.
+    optional DataSubscriptionState opportunistic_data_sub = 9;
+}
+
+/**
  * Pulls bytes transferred via bluetooth. It is pulled from Bluetooth controller.
  *
  * Pulled from:
@@ -5982,6 +6032,12 @@
     optional int32 user_locked_fields = 6;
     // Indicates if the channel was deleted by the app.
     optional bool is_deleted = 7;
+    // Indicates if the channel was marked as a conversation by the app.
+    optional bool is_conversation = 8;
+    // Indicates if the channel is a conversation that was demoted by the user.
+    optional bool is_demoted_conversation = 9;
+    // Indicates if the channel is a conversation that was marked as important by the user.
+    optional bool is_important_conversation = 10;
 }
 
 /**
@@ -9121,6 +9177,28 @@
 }
 
 /**
+ * Logs when a tune occurs through device's Frontend.
+ * This is atom ID 276.
+ *
+ * Logged from:
+ *   frameworks/base/media/java/android/media/tv/tuner/Tuner.java
+ */
+message TvTunerStateChanged {
+    enum State {
+        UNKNOWN = 0;
+        TUNING = 1; // Signal is tuned
+        LOCKED = 2;    // the signal is locked
+        NOT_LOCKED = 3; // the signal isn’t locked.
+        SIGNAL_LOST = 4; // the signal was locked, but is lost now.
+        SCANNING = 5; // the signal is scanned
+        SCAN_STOPPED = 6; // the scan is stopped.
+    }
+    // The uid of the application that sent this custom atom.
+    optional int32 uid = 1 [(is_uid) = true];
+    //  new state
+    optional State state = 2;
+}
+/**
  * Logs when an app is frozen or unfrozen.
  *
  * Logged from:
@@ -9756,3 +9834,25 @@
     }
     optional AudioType type = 4;
 }
+
+/**
+  * Pulls bytes transferred over WiFi and mobile networks sliced by uid, is_metered, and tag.
+  *
+  * Pulled from:
+  *   StatsPullAtomService, which uses NetworkStatsService to query NetworkStats.
+  */
+message BytesTransferByTagAndMetered {
+    optional int32 uid = 1 [(is_uid) = true];
+
+    optional bool is_metered = 2;
+
+    optional int32 tag = 3;
+
+    optional int64 rx_bytes = 4;
+
+    optional int64 rx_packets = 5;
+
+    optional int64 tx_bytes = 6;
+
+    optional int64 tx_packets = 7;
+}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 21ffff3..d865c21 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -122,11 +122,11 @@
 }
 
 void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                                         const HashableDimensionKey& primaryKey, int oldState,
-                                         int newState) {
+                                         const HashableDimensionKey& primaryKey,
+                                         const FieldValue& oldState, const FieldValue& newState) {
     VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d",
          (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
-         oldState, newState);
+         oldState.mValue.int_value, newState.mValue.int_value);
 }
 
 void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index f9a8842..26b3d3c 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -53,8 +53,8 @@
     virtual ~CountMetricProducer();
 
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                        const HashableDimensionKey& primaryKey, int oldState,
-                        int newState) override;
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState) override;
 
 protected:
     void onMatchedLogEventInternalLocked(
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 0de92f3..6633659 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -161,13 +161,12 @@
 
 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);
+                                            const FieldValue& oldState,
+                                            const FieldValue& newState) {
     // Check if this metric has a StateMap. If so, map the new state value to
     // the correct state group id.
-    mapStateValue(atomId, &value);
+    FieldValue newStateCopy = newState;
+    mapStateValue(atomId, &newStateCopy);
 
     flushIfNeededLocked(eventTimeNs);
 
@@ -185,7 +184,7 @@
         if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
             continue;
         }
-        whatIt.second->onStateChanged(eventTimeNs, atomId, value);
+        whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy);
     }
 }
 
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 6f84076..53f0f28 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -55,8 +55,8 @@
                                          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;
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState) override;
 
 protected:
     void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 91c98ea2..5fabb5f 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -182,8 +182,8 @@
     };
 
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                        const HashableDimensionKey& primaryKey, const int32_t oldState,
-                        const int32_t newState){};
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState){};
 
     // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
     // This method clears all the past buckets.
@@ -442,7 +442,7 @@
     bool mIsActive;
 
     // The slice_by_state atom ids defined in statsd_config.
-    std::vector<int32_t> mSlicedStateAtoms;
+    const std::vector<int32_t> mSlicedStateAtoms;
 
     // Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>).
     const std::unordered_map<int32_t, std::unordered_map<int, int64_t>> mStateGroupMap;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index d7ad27b..e8c575a 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -71,6 +71,8 @@
       mLastReportTimeNs(currentTimeNs),
       mLastReportWallClockNs(getWallClockNs()),
       mPullerManager(pullerManager),
+      mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
+                          config.whitelisted_atom_ids().end()),
       mShouldPersistHistory(config.persist_locally()) {
     // Init the ttl end timestamp.
     refreshTtl(timeBaseNs);
@@ -366,11 +368,16 @@
 
 
 bool MetricsManager::checkLogCredentials(const LogEvent& event) {
+    // TODO(b/154856835): Remove this check once we get whitelist from the config.
     if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) !=
       android::util::AtomsInfo::kWhitelistedAtoms.end())
     {
         return true;
     }
+
+    if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) {
+        return true;
+    }
     std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
     if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) {
         VLOG("log source %d not on the whitelist", event.GetUid());
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index ef03d20..c30532a 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -189,6 +189,8 @@
     // To guard access to mAllowedLogSources
     mutable std::mutex mAllowedLogSourcesMutex;
 
+    const std::set<int32_t> mWhitelistedAtomIds;
+
     // We can pull any atom from these uids.
     std::set<int32_t> mDefaultPullUids;
 
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index bf636a4..f03ce45 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -182,15 +182,26 @@
 }
 
 void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId,
-                                         const HashableDimensionKey& primaryKey, int oldState,
-                                         int newState) {
+                                         const HashableDimensionKey& primaryKey,
+                                         const FieldValue& oldState, const FieldValue& newState) {
     VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d",
          (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
-         oldState, newState);
+         oldState.mValue.int_value, newState.mValue.int_value);
     // If condition is not true, we do not need to pull for this state change.
     if (mCondition != ConditionState::kTrue) {
         return;
     }
+
+    // If old and new states are in the same StateGroup, then we do not need to
+    // pull for this state change.
+    FieldValue oldStateCopy = oldState;
+    FieldValue newStateCopy = newState;
+    mapStateValue(atomId, &oldStateCopy);
+    mapStateValue(atomId, &newStateCopy);
+    if (oldStateCopy == newStateCopy) {
+        return;
+    }
+
     bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs;
     if (isEventLate) {
         VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index c8dc8cc..751fef2 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -90,7 +90,7 @@
     };
 
     void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
-                        int oldState, int newState) override;
+                        const FieldValue& oldState, const FieldValue& newState) override;
 
 protected:
     void onMatchedLogEventInternalLocked(
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3ab44f4..210d382 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -791,10 +791,19 @@
         }
         noReportMetricIds.insert(no_report_metric);
     }
+
+    const set<int> whitelistedAtomIds(config.whitelisted_atom_ids().begin(),
+                                      config.whitelisted_atom_ids().end());
     for (const auto& it : allMetricProducers) {
         // Register metrics to StateTrackers
         for (int atomId : it->getSlicedStateAtoms()) {
-            StateManager::getInstance().registerListener(atomId, it);
+            // Register listener for non-whitelisted atoms only. Using whitelisted atom as a sliced
+            // state atom is not allowed.
+            if (whitelistedAtomIds.find(atomId) == whitelistedAtomIds.end()) {
+                StateManager::getInstance().registerListener(atomId, it);
+            } else {
+                return false;
+            }
         }
     }
     return true;
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
index d1af196..6388001 100644
--- a/cmds/statsd/src/state/StateListener.h
+++ b/cmds/statsd/src/state/StateListener.h
@@ -45,8 +45,8 @@
      * [newState]: Current state value after state change
      */
     virtual void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                                const HashableDimensionKey& primaryKey, int oldState,
-                                int newState) = 0;
+                                const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                                const FieldValue& newState) = 0;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index b63713b..41e525c 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -35,31 +35,30 @@
     HashableDimensionKey primaryKey;
     filterPrimaryKey(event.getValues(), &primaryKey);
 
-    FieldValue stateValue;
-    if (!getStateFieldValueFromLogEvent(event, &stateValue)) {
+    FieldValue newState;
+    if (!getStateFieldValueFromLogEvent(event, &newState)) {
         ALOGE("StateTracker error extracting state from log event. Missing exclusive state field.");
         clearStateForPrimaryKey(eventTimeNs, primaryKey);
         return;
     }
 
-    mField.setField(stateValue.mField.getField());
+    mField.setField(newState.mField.getField());
 
-    if (stateValue.mValue.getType() != INT) {
+    if (newState.mValue.getType() != INT) {
         ALOGE("StateTracker error extracting state from log event. Type: %d",
-              stateValue.mValue.getType());
+              newState.mValue.getType());
         clearStateForPrimaryKey(eventTimeNs, primaryKey);
         return;
     }
 
-    const int32_t resetState = event.getResetState();
-    if (resetState != -1) {
+    if (int resetState = event.getResetState(); resetState != -1) {
         VLOG("StateTracker new reset state: %d", resetState);
-        handleReset(eventTimeNs, resetState);
+        const FieldValue resetStateFieldValue(mField, Value(resetState));
+        handleReset(eventTimeNs, resetStateFieldValue);
         return;
     }
 
-    const int32_t newState = stateValue.mValue.int_value;
-    const bool nested = stateValue.mAnnotations.isNested();
+    const bool nested = newState.mAnnotations.isNested();
     StateValueInfo* stateValueInfo = &mStateMap[primaryKey];
     updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo);
 }
@@ -85,7 +84,7 @@
     return false;
 }
 
-void StateTracker::handleReset(const int64_t eventTimeNs, const int32_t newState) {
+void StateTracker::handleReset(const int64_t eventTimeNs, const FieldValue& newState) {
     VLOG("StateTracker handle reset");
     for (auto& [primaryKey, stateValueInfo] : mStateMap) {
         updateStateForPrimaryKey(eventTimeNs, primaryKey, newState,
@@ -102,8 +101,9 @@
 
     // If there is no entry for the primaryKey in mStateMap, then the state is already
     // kStateUnknown.
+    const FieldValue state(mField, Value(kStateUnknown));
     if (it != mStateMap.end()) {
-        updateStateForPrimaryKey(eventTimeNs, primaryKey, kStateUnknown,
+        updateStateForPrimaryKey(eventTimeNs, primaryKey, state,
                                  false /* nested; treat this state change as not nested */,
                                  &it->second);
     }
@@ -111,22 +111,26 @@
 
 void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs,
                                             const HashableDimensionKey& primaryKey,
-                                            const int32_t newState, const bool nested,
+                                            const FieldValue& newState, const bool nested,
                                             StateValueInfo* stateValueInfo) {
-    const int32_t oldState = stateValueInfo->state;
+    FieldValue oldState;
+    oldState.mField = mField;
+    oldState.mValue.setInt(stateValueInfo->state);
+    const int32_t oldStateValue = stateValueInfo->state;
+    const int32_t newStateValue = newState.mValue.int_value;
 
-    if (kStateUnknown == newState) {
+    if (kStateUnknown == newStateValue) {
         mStateMap.erase(primaryKey);
     }
 
     // Update state map for non-nested counting case.
     // Every state event triggers a state overwrite.
     if (!nested) {
-        stateValueInfo->state = newState;
+        stateValueInfo->state = newStateValue;
         stateValueInfo->count = 1;
 
         // Notify listeners if state has changed.
-        if (oldState != newState) {
+        if (oldStateValue != newStateValue) {
             notifyListeners(eventTimeNs, primaryKey, oldState, newState);
         }
         return;
@@ -142,26 +146,26 @@
     // In atoms.proto, a state atom with nested counting enabled
     // must only have 2 states. There is no enforcemnt here of this requirement.
     // The atom must be logged correctly.
-    if (kStateUnknown == newState) {
-        if (kStateUnknown != oldState) {
+    if (kStateUnknown == newStateValue) {
+        if (kStateUnknown != oldStateValue) {
             notifyListeners(eventTimeNs, primaryKey, oldState, newState);
         }
-    } else if (oldState == kStateUnknown) {
-        stateValueInfo->state = newState;
+    } else if (oldStateValue == kStateUnknown) {
+        stateValueInfo->state = newStateValue;
         stateValueInfo->count = 1;
         notifyListeners(eventTimeNs, primaryKey, oldState, newState);
-    } else if (oldState == newState) {
+    } else if (oldStateValue == newStateValue) {
         stateValueInfo->count++;
     } else if (--stateValueInfo->count == 0) {
-        stateValueInfo->state = newState;
+        stateValueInfo->state = newStateValue;
         stateValueInfo->count = 1;
         notifyListeners(eventTimeNs, primaryKey, oldState, newState);
     }
 }
 
 void StateTracker::notifyListeners(const int64_t eventTimeNs,
-                                   const HashableDimensionKey& primaryKey, const int32_t oldState,
-                                   const int32_t newState) {
+                                   const HashableDimensionKey& primaryKey,
+                                   const FieldValue& oldState, const FieldValue& newState) {
     for (auto l : mListeners) {
         auto sl = l.promote();
         if (sl != nullptr) {
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index c5f6315..abd579e 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -72,19 +72,19 @@
     std::set<wp<StateListener>> mListeners;
 
     // Reset all state values in map to the given state.
-    void handleReset(const int64_t eventTimeNs, const int32_t newState);
+    void handleReset(const int64_t eventTimeNs, const FieldValue& newState);
 
     // Clears the state value mapped to the given primary key by setting it to kStateUnknown.
     void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey);
 
     // Update the StateMap based on the received state value.
     void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
-                                  const int32_t newState, const bool nested,
+                                  const FieldValue& newState, const bool nested,
                                   StateValueInfo* stateValueInfo);
 
     // Notify registered state listeners of state change.
     void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
-                         const int32_t oldState, const int32_t newState);
+                         const FieldValue& oldState, const FieldValue& newState);
 };
 
 bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index c7407bd..7c0057d 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -489,6 +489,8 @@
 
   repeated PullAtomPackages pull_atom_packages = 23;
 
+  repeated int32 whitelisted_atom_ids = 24;
+
   // Field number 1000 is reserved for later use.
   reserved 1000;
 }
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 3890fbe..b3b095b 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -29,6 +29,7 @@
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
 #include "src/metrics/metrics_manager_util.h"
+#include "src/state/StateManager.h"
 #include "statsd_test_util.h"
 
 using namespace testing;
@@ -591,6 +592,58 @@
     EXPECT_TRUE(isSubset(defaultPullUids, set<int32_t>(atom3Uids.begin(), atom3Uids.end())));
 }
 
+TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodConfig();
+    config.add_whitelisted_atom_ids(3);
+    config.add_whitelisted_atom_ids(4);
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    LogEvent event(0 /* uid */, 0 /* pid */);
+    CreateNoValuesLogEvent(&event, 10 /* atom id */, 0 /* timestamp */);
+    EXPECT_FALSE(metricsManager.checkLogCredentials(event));
+
+    CreateNoValuesLogEvent(&event, 3 /* atom id */, 0 /* timestamp */);
+    EXPECT_TRUE(metricsManager.checkLogCredentials(event));
+
+    CreateNoValuesLogEvent(&event, 4 /* atom id */, 0 /* timestamp */);
+    EXPECT_TRUE(metricsManager.checkLogCredentials(event));
+}
+
+TEST(MetricsManagerTest, TestWhitelistedAtomStateTracker) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodConfig();
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.add_whitelisted_atom_ids(3);
+    config.add_whitelisted_atom_ids(4);
+
+    State state;
+    state.set_id(1);
+    state.set_atom_id(3);
+
+    *config.add_state() = state;
+
+    config.mutable_count_metric(0)->add_slice_by_state(state.id());
+
+    StateManager::getInstance().clear();
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_FALSE(metricsManager.isConfigValid());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index b6e1075..474aa22 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -3898,14 +3898,12 @@
                 data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5));
                 return true;
             }))
-            // Screen state change to VR.
-            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
-                                vector<std::shared_ptr<LogEvent>>* data, bool) {
-                EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
-                data->clear();
-                data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9));
-                return true;
-            }))
+            // Screen state change to VR has no pull because it is in the same
+            // state group as ON.
+
+            // Screen state change to ON has no pull because it is in the same
+            // state group as VR.
+
             // Screen state change to OFF.
             .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
                                 vector<std::shared_ptr<LogEvent>>* data, bool) {
@@ -3969,23 +3967,33 @@
     EXPECT_EQ(true, it->second[0].hasValue);
     EXPECT_EQ(2, it->second[0].value.long_value);
 
-    // Bucket status after screen state change ON->VR (also ON).
+    // Bucket status after screen state change ON->VR.
+    // Both ON and VR are in the same state group, so the base should not change.
     screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
                                                 android::view::DisplayStateEnum::DISPLAY_STATE_VR);
     StateManager::getInstance().onLogEvent(*screenEvent);
-    ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+    ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
     EXPECT_EQ(true, itBase->second[0].hasBase);
-    EXPECT_EQ(9, itBase->second[0].base.long_value);
-    // Value for dimension, state key {{}, ON GROUP}
-    EXPECT_EQ(screenOnGroup.group_id(),
-              it->first.getStateValuesKey().getValues()[0].mValue.long_value);
-    EXPECT_EQ(true, it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_EQ(5, itBase->second[0].base.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
-    it++;
+    EXPECT_EQ(true, it->second[0].hasValue);
+    EXPECT_EQ(2, it->second[0].value.long_value);
+
+    // Bucket status after screen state change VR->ON.
+    // Both ON and VR are in the same state group, so the base should not change.
+    screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12,
+                                                android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    StateManager::getInstance().onLogEvent(*screenEvent);
+    EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+    // Base for dimension key {}
+    it = valueProducer->mCurrentSlicedBucket.begin();
+    itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+    EXPECT_EQ(true, itBase->second[0].hasBase);
+    EXPECT_EQ(5, itBase->second[0].base.long_value);
+    // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(true, it->second[0].hasValue);
     EXPECT_EQ(2, it->second[0].value.long_value);
 
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index 13e8f5c..530ac5e 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -50,8 +50,9 @@
     std::vector<Update> updates;
 
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                        const HashableDimensionKey& primaryKey, int oldState, int newState) {
-        updates.emplace_back(primaryKey, newState);
+                        const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+                        const FieldValue& newState) {
+        updates.emplace_back(primaryKey, newState.mValue.int_value);
     }
 };
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b0ce7d1..97b704c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -601,6 +601,20 @@
     @TestApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
 
+    // TODO: remove this when development is done.
+    // These are debug flags used between OomAdjuster and AppOpsService to detect and report absence
+    // of the real flags.
+    /** @hide */
+    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q = 1 << 27;
+    /** @hide */
+    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q = 1 << 28;
+    /** @hide */
+    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
+    /** @hide */
+    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
+    /** @hide */
+    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
+
     /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/
     @TestApi
     public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION
@@ -623,6 +637,51 @@
     public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = PROCESS_CAPABILITY_FOREGROUND_CAMERA
             | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
 
+    /**
+     * Print capability bits in human-readable form.
+     * @hide
+     */
+    public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
+        pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
+        pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
+        pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
+    }
+
+    /**
+     * Print capability bits in human-readable form.
+     * @hide
+     */
+    public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
+        printCapabilitiesSummary(pw, caps);
+        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) {
+            pw.print(" !L");
+        }
+        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
+            pw.print(" !C");
+        }
+        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) != 0) {
+            pw.print(" !Cq");
+        }
+        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
+            pw.print(" !M");
+        }
+        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) != 0) {
+            pw.print(" !Mq");
+        }
+        final int remain = caps & ~(PROCESS_CAPABILITY_FOREGROUND_LOCATION
+                | PROCESS_CAPABILITY_FOREGROUND_CAMERA
+                | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION
+                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA
+                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q
+                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q);
+        if (remain != 0) {
+            pw.print('+');
+            pw.print(remain);
+        }
+    }
+
     // NOTE: If PROCESS_STATEs are added, then new fields must be added
     // to frameworks/base/core/proto/android/app/enums.proto and the following method must
     // be updated to correctly map between them.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index eea1d69..8e43ca3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1538,6 +1538,12 @@
             IoUtils.closeQuietly(pfd);
         }
 
+        @Override
+        public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
+            PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args);
+            IoUtils.closeQuietly(pfd);
+        }
+
         private File getDatabasesDir(Context context) {
             // There's no simple way to get the databases/ path, so do it this way.
             return context.getDatabasePath("a").getParentFile();
@@ -5902,6 +5908,12 @@
         }
     }
 
+    /**
+     * Sets the supplied {@code overrideConfig} as pending for the {@code activityToken}. Calling
+     * this method prevents any calls to
+     * {@link #handleActivityConfigurationChanged(IBinder, Configuration, int, boolean)} from
+     * processing any configurations older than {@code overrideConfig}.
+     */
     @Override
     public void updatePendingActivityConfiguration(IBinder activityToken,
             Configuration overrideConfig) {
@@ -5918,13 +5930,22 @@
         }
 
         synchronized (r) {
+            if (r.mPendingOverrideConfig != null
+                    && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
+                if (DEBUG_CONFIGURATION) {
+                    Slog.v(TAG, "Activity has newer configuration pending so drop this"
+                            + " transaction. overrideConfig=" + overrideConfig
+                            + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig);
+                }
+                return;
+            }
             r.mPendingOverrideConfig = overrideConfig;
         }
     }
 
     @Override
     public void handleActivityConfigurationChanged(IBinder activityToken,
-            Configuration overrideConfig, int displayId) {
+            @NonNull Configuration overrideConfig, int displayId) {
         handleActivityConfigurationChanged(activityToken, overrideConfig, displayId,
                 // This is the only place that uses alwaysReportChange=true. The entry point should
                 // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
@@ -5935,15 +5956,18 @@
     }
 
     /**
-     * Handle new activity configuration and/or move to a different display.
+     * Handle new activity configuration and/or move to a different display. This method is a noop
+     * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been called with
+     * a newer config than {@code overrideConfig}.
+     *
      * @param activityToken Target activity token.
      * @param overrideConfig Activity override config.
      * @param displayId Id of the display where activity was moved to, -1 if there was no move and
      *                  value didn't change.
      * @param alwaysReportChange If the configuration is changed, always report to activity.
      */
-    void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig,
-            int displayId, boolean alwaysReportChange) {
+    void handleActivityConfigurationChanged(IBinder activityToken,
+            @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
         ActivityClientRecord r = mActivities.get(activityToken);
         // Check input params.
         if (r == null || r.activity == null) {
@@ -5954,9 +5978,13 @@
                 && displayId != r.activity.getDisplayId();
 
         synchronized (r) {
-            if (r.mPendingOverrideConfig != null
-                    && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
-                overrideConfig = r.mPendingOverrideConfig;
+            if (overrideConfig.isOtherSeqNewer(r.mPendingOverrideConfig)) {
+                if (DEBUG_CONFIGURATION) {
+                    Slog.v(TAG, "Activity has newer configuration pending so drop this"
+                            + " transaction. overrideConfig=" + overrideConfig
+                            + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig);
+                }
+                return;
             }
             r.mPendingOverrideConfig = null;
         }
@@ -6202,6 +6230,12 @@
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
         if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
 
+        if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+            for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) {
+                pic.clear();
+            }
+        }
+
         ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
 
         final int N = callbacks.size();
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 3ce7689..be1681b 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -229,7 +229,16 @@
     void unregisterTaskStackListener(in ITaskStackListener listener);
     void setTaskResizeable(int taskId, int resizeableMode);
     void toggleFreeformWindowingMode(in IBinder token);
-    void resizeTask(int taskId, in Rect bounds, int resizeMode);
+
+    /**
+     * Resize the task with given bounds
+     *
+     * @param taskId The id of the task to set the bounds for.
+     * @param bounds The new bounds.
+     * @param resizeMode Resize mode defined as {@code ActivityTaskManager#RESIZE_MODE_*} constants.
+     * @return Return true on success. Otherwise false.
+     */
+    boolean resizeTask(int taskId, in Rect bounds, int resizeMode);
     void moveStackToDisplay(int stackId, int displayId);
     void removeStack(int stackId);
 
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 1f6e4ca..6e9157e 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -119,6 +119,7 @@
             boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
             in String[] args);
     void dumpGfxInfo(in ParcelFileDescriptor fd, in String[] args);
+    void dumpCacheInfo(in ParcelFileDescriptor fd, in String[] args);
     void dumpProvider(in ParcelFileDescriptor fd, IBinder servicetoken,
             in String[] args);
     void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8dfce14..4c3e888 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -58,7 +58,9 @@
 
     void setShowBadge(String pkg, int uid, boolean showBadge);
     boolean canShowBadge(String pkg, int uid);
-    boolean hasSentMessage(String pkg, int uid);
+    boolean isInInvalidMsgState(String pkg, int uid);
+    boolean hasUserDemotedInvalidMsgApp(String pkg, int uid);
+    void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted);
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     /**
      * Updates the notification's enabled state. Additionally locks importance for all of the
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 3110e18..01cf2b94a 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -26,12 +26,20 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
 
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
+import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
@@ -197,6 +205,14 @@
     @GuardedBy("sCorkLock")
     private static final HashMap<String, Integer> sCorks = new HashMap<>();
 
+    /**
+     * Weakly references all cache objects in the current process, allowing us to iterate over
+     * them all for purposes like issuing debug dumps and reacting to memory pressure.
+     */
+    @GuardedBy("sCorkLock")
+    private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches =
+            new WeakHashMap<>();
+
     private final Object mLock = new Object();
 
     /**
@@ -225,6 +241,11 @@
     private boolean mDisabled = false;
 
     /**
+     * Maximum number of entries the cache will maintain.
+     */
+    private final int mMaxEntries;
+
+    /**
      * Make a new property invalidated cache.
      *
      * @param maxEntries Maximum number of entries to cache; LRU discard
@@ -232,6 +253,7 @@
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
         mPropertyName = propertyName;
+        mMaxEntries = maxEntries;
         mCache = new LinkedHashMap<Query, Result>(
             2 /* start small */,
             0.75f /* default load factor */,
@@ -241,6 +263,9 @@
                     return size() > maxEntries;
                 }
             };
+        synchronized (sCorkLock) {
+            sCaches.put(this, null);
+        }
     }
 
     /**
@@ -248,6 +273,9 @@
      */
     public final void clear() {
         synchronized (mLock) {
+            if (DEBUG) {
+                Log.d(TAG, "clearing cache for " + mPropertyName);
+            }
             mCache.clear();
         }
     }
@@ -710,4 +738,87 @@
         Log.d(TAG, "disabling all caches in the process");
         sEnabled = false;
     }
+
+    /**
+     * Returns a list of caches alive at the current time.
+     */
+    public static ArrayList<PropertyInvalidatedCache> getActiveCaches() {
+        synchronized (sCorkLock) {
+            return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet());
+        }
+    }
+
+    /**
+     * Returns a list of the active corks in a process.
+     */
+    public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() {
+        synchronized (sCorkLock) {
+            return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet());
+        }
+    }
+
+    private void dumpContents(PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            pw.println(String.format("  Cache Property Name: %s", cacheName()));
+            pw.println(String.format("    Last Observed Nonce: %d", mLastSeenNonce));
+            pw.println(String.format("    Current Size: %d, Max Size: %d",
+                    mCache.entrySet().size(), mMaxEntries));
+            pw.println(String.format("    Enabled: %s", mDisabled ? "false" : "true"));
+
+            Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet();
+            if (cacheEntries.size() == 0) {
+                pw.println("");
+                return;
+            }
+
+            pw.println("");
+            pw.println("    Contents:");
+            for (Map.Entry<Query, Result> entry : cacheEntries) {
+                String key = Objects.toString(entry.getKey());
+                String value = Objects.toString(entry.getValue());
+
+                pw.println(String.format("      Key: %s\n      Value: %s\n", key, value));
+            }
+        }
+    }
+
+    /**
+     * Dumps contents of every cache in the process to the provided FileDescriptor.
+     */
+    public static void dumpCacheInfo(FileDescriptor fd, String[] args) {
+        ArrayList<PropertyInvalidatedCache> activeCaches;
+        ArrayList<Map.Entry<String, Integer>> activeCorks;
+
+        try  (
+            FileOutputStream fout = new FileOutputStream(fd);
+            PrintWriter pw = new FastPrintWriter(fout);
+        ) {
+            if (!sEnabled) {
+                pw.println("  Caching is disabled in this process.");
+                return;
+            }
+
+            synchronized (sCorkLock) {
+                activeCaches = getActiveCaches();
+                activeCorks = getActiveCorks();
+
+                if (activeCorks.size() > 0) {
+                    pw.println("  Corking Status:");
+                    for (int i = 0; i < activeCorks.size(); i++) {
+                        Map.Entry<String, Integer> entry = activeCorks.get(i);
+                        pw.println(String.format("    Property Name: %s Count: %d",
+                                entry.getKey(), entry.getValue()));
+                    }
+                }
+            }
+
+            for (int i = 0; i < activeCaches.size(); i++) {
+                PropertyInvalidatedCache currentCache = activeCaches.get(i);
+                currentCache.dumpContents(pw, args);
+                pw.flush();
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
+        }
+    }
 }
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 0d4e16b..8b52242 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -19,6 +19,7 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.INVALID_DISPLAY;
 
+import android.annotation.NonNull;
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
 import android.os.IBinder;
@@ -37,6 +38,8 @@
 
     @Override
     public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
+        // Notify the client of an upcoming change in the token configuration. This ensures that
+        // batches of config change items only process the newest configuration.
         client.updatePendingActivityConfiguration(token, mConfiguration);
     }
 
@@ -55,7 +58,11 @@
     private ActivityConfigurationChangeItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static ActivityConfigurationChangeItem obtain(Configuration config) {
+    public static ActivityConfigurationChangeItem obtain(@NonNull Configuration config) {
+        if (config == null) {
+            throw new IllegalArgumentException("Config must not be null.");
+        }
+
         ActivityConfigurationChangeItem instance =
                 ObjectPool.obtain(ActivityConfigurationChangeItem.class);
         if (instance == null) {
@@ -68,7 +75,7 @@
 
     @Override
     public void recycle() {
-        mConfiguration = null;
+        mConfiguration = Configuration.EMPTY;
         ObjectPool.recycle(this);
     }
 
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index f6d3dbd..9a457a3 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
 import android.os.IBinder;
@@ -36,6 +37,13 @@
     private Configuration mConfiguration;
 
     @Override
+    public void preExecute(ClientTransactionHandler client, IBinder token) {
+        // Notify the client of an upcoming change in the token configuration. This ensures that
+        // batches of config change items only process the newest configuration.
+        client.updatePendingActivityConfiguration(token, mConfiguration);
+    }
+
+    @Override
     public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
@@ -49,7 +57,12 @@
     private MoveToDisplayItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static MoveToDisplayItem obtain(int targetDisplayId, Configuration configuration) {
+    public static MoveToDisplayItem obtain(int targetDisplayId,
+            @NonNull Configuration configuration) {
+        if (configuration == null) {
+            throw new IllegalArgumentException("Configuration must not be null");
+        }
+
         MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
         if (instance == null) {
             instance = new MoveToDisplayItem();
@@ -63,7 +76,7 @@
     @Override
     public void recycle() {
         mTargetDisplayId = 0;
-        mConfiguration = null;
+        mConfiguration = Configuration.EMPTY;
         ObjectPool.recycle(this);
     }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1e9cddbb..2f488cd 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -51,7 +51,9 @@
 import android.content.pm.parsing.PackageInfoWithoutStateUtils;
 import android.content.pm.parsing.ParsingPackage;
 import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
@@ -383,6 +385,9 @@
      * <p>
      * Note: this flag may cause less information about currently installed
      * applications to be returned.
+     * <p>
+     * Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES
+     * permission to see uninstalled packages.
      */
     public static final int MATCH_UNINSTALLED_PACKAGES = 0x00002000;
 
@@ -6061,7 +6066,8 @@
         boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
                 || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
 
-        ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(
+        ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
+        ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
                 new File(archiveFilePath), 0, collectCertificates);
         if (result.isError()) {
             return null;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index cb29431..5a79475 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -131,14 +131,23 @@
     public static final String TAG = ParsingUtils.TAG;
 
     /**
-     * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
-     * request, without caching the input object and without querying the internal system state
-     * for feature support.
+     * @see #parseDefault(ParseInput, File, int, boolean)
      */
     @NonNull
     public static ParseResult<ParsingPackage> parseDefaultOneTime(File file,
             @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
         ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
+        return parseDefault(input, file, parseFlags, collectCertificates);
+    }
+
+    /**
+     * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
+     * request, without caching the input object and without querying the internal system state
+     * for feature support.
+     */
+    @NonNull
+    public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
+            @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
         ParseResult<ParsingPackage> result;
 
         ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() {
diff --git a/core/java/android/content/pm/parsing/result/ParseInput.java b/core/java/android/content/pm/parsing/result/ParseInput.java
index d5898b7..0fb18ae 100644
--- a/core/java/android/content/pm/parsing/result/ParseInput.java
+++ b/core/java/android/content/pm/parsing/result/ParseInput.java
@@ -16,6 +16,7 @@
 
 package android.content.pm.parsing.result;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
@@ -69,6 +70,25 @@
         @ChangeId
         @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
         public static final long RESOURCES_ARSC_COMPRESSED = 132742131;
+
+        /**
+         * TODO(chiuwinson): This is required because PackageManager#getPackageArchiveInfo
+         *   cannot read the targetSdk info from the changeId because it requires the
+         *   READ_COMPAT_CHANGE_CONFIG which cannot be obtained automatically without entering the
+         *   server process. This should be removed once an alternative is found, or if the API
+         *   is removed.
+         * @return the targetSdk that this change is gated on (> check), or -1 if disabled
+         */
+        @IntRange(from = -1, to = Integer.MAX_VALUE)
+        public static int getTargetSdkForChange(long changeId) {
+            if (changeId == MISSING_APP_TAG
+                    || changeId == EMPTY_INTENT_ACTION_CATEGORY
+                    || changeId == RESOURCES_ARSC_COMPRESSED) {
+                return Build.VERSION_CODES.Q;
+            }
+
+            return -1;
+        }
     }
 
     <ResultType> ParseResult<ResultType> success(ResultType result);
diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
index 91e571b..14992fb 100644
--- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
+++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
@@ -65,6 +65,21 @@
     private Integer mTargetSdkVersion;
 
     /**
+     * Specifically for {@link PackageManager#getPackageArchiveInfo(String, int)} where
+     * {@link IPlatformCompat} cannot be used because the cross-package READ_COMPAT_CHANGE_CONFIG
+     * permission cannot be obtained.
+     */
+    public static ParseTypeImpl forParsingWithoutPlatformCompat() {
+        return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> {
+            int gateSdkVersion = DeferredError.getTargetSdkForChange(changeId);
+            if (gateSdkVersion == -1) {
+                return false;
+            }
+            return targetSdkVersion > gateSdkVersion;
+        });
+    }
+
+    /**
      * Assumes {@link Context#PLATFORM_COMPAT_SERVICE} is available to the caller. For use
      * with {@link android.content.pm.parsing.ApkLiteParseUtils} or similar where parsing is
      * done outside of {@link com.android.server.pm.PackageManagerService}.
diff --git a/core/java/android/debug/AdbNotifications.java b/core/java/android/debug/AdbNotifications.java
index fed5f5f..9f1a5f8 100644
--- a/core/java/android/debug/AdbNotifications.java
+++ b/core/java/android/debug/AdbNotifications.java
@@ -17,11 +17,13 @@
 package android.debug;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -42,8 +44,9 @@
      * Builds a notification to show connected state for adb over a transport type.
      * @param context the context
      * @param transportType the adb transport type.
-     * @return a newly created Notification for the transport type.
+     * @return a newly created Notification for the transport type, or null on error.
      */
+    @Nullable
     public static Notification createNotification(@NonNull Context context,
             byte transportType) {
         Resources resources = context.getResources();
@@ -66,10 +69,16 @@
 
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        intent.setPackage(context.getPackageManager().resolveActivity(intent,
-                PackageManager.MATCH_SYSTEM_ONLY).activityInfo.packageName);
-        PendingIntent pIntent = PendingIntent.getActivityAsUser(context, 0, intent,
-                PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+        ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
+                PackageManager.MATCH_SYSTEM_ONLY);
+        // Settings app may not be available (e.g. device policy manager removes it)
+        PendingIntent pIntent = null;
+        if (resolveInfo != null) {
+            intent.setPackage(resolveInfo.activityInfo.packageName);
+            pIntent = PendingIntent.getActivityAsUser(context, 0, intent,
+                    PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+        }
+
 
         return new Notification.Builder(context, SystemNotificationChannels.DEVELOPER_IMPORTANT)
                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 4bed985..f9ed2f8 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -314,6 +314,92 @@
         }
 
         @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (!(obj instanceof ModuleProperties)) {
+                return false;
+            }
+            ModuleProperties other = (ModuleProperties) obj;
+            if (mId != other.mId) {
+                return false;
+            }
+            if (!mImplementor.equals(other.mImplementor)) {
+                return false;
+            }
+            if (!mDescription.equals(other.mDescription)) {
+                return false;
+            }
+            if (!mUuid.equals(other.mUuid)) {
+                return false;
+            }
+            if (mVersion != other.mVersion) {
+                return false;
+            }
+            if (!mSupportedModelArch.equals(other.mSupportedModelArch)) {
+                return false;
+            }
+            if (mMaxSoundModels != other.mMaxSoundModels) {
+                return false;
+            }
+            if (mMaxKeyphrases != other.mMaxKeyphrases) {
+                return false;
+            }
+            if (mMaxUsers != other.mMaxUsers) {
+                return false;
+            }
+            if (mRecognitionModes != other.mRecognitionModes) {
+                return false;
+            }
+            if (mSupportsCaptureTransition != other.mSupportsCaptureTransition) {
+                return false;
+            }
+            if (mMaxBufferMillis != other.mMaxBufferMillis) {
+                return false;
+            }
+            if (mSupportsConcurrentCapture != other.mSupportsConcurrentCapture) {
+                return false;
+            }
+            if (mPowerConsumptionMw != other.mPowerConsumptionMw) {
+                return false;
+            }
+            if (mReturnsTriggerInEvent != other.mReturnsTriggerInEvent) {
+                return false;
+            }
+            if (mAudioCapabilities != other.mAudioCapabilities) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + mId;
+            result = prime * result + mImplementor.hashCode();
+            result = prime * result + mDescription.hashCode();
+            result = prime * result + mUuid.hashCode();
+            result = prime * result + mVersion;
+            result = prime * result + mSupportedModelArch.hashCode();
+            result = prime * result + mMaxSoundModels;
+            result = prime * result + mMaxKeyphrases;
+            result = prime * result + mMaxUsers;
+            result = prime * result + mRecognitionModes;
+            result = prime * result + (mSupportsCaptureTransition ? 1 : 0);
+            result = prime * result + mMaxBufferMillis;
+            result = prime * result + (mSupportsConcurrentCapture ? 1 : 0);
+            result = prime * result + mPowerConsumptionMw;
+            result = prime * result + (mReturnsTriggerInEvent ? 1 : 0);
+            result = prime * result + mAudioCapabilities;
+            return result;
+        }
+
+        @Override
         public String toString() {
             return "ModuleProperties [id=" + getId() + ", implementor=" + getImplementor()
                     + ", description=" + getDescription() + ", uuid=" + getUuid()
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 25bf430..4832e56 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1729,9 +1729,15 @@
      * <p>As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method can
      * now automatically identify goats using advanced goat recognition technology.</p>
      *
-     * @return Returns true if the user making this call is a goat.
+     * <p>As of {@link android.os.Build.VERSION_CODES#R}, this method always returns
+     * {@code false} in order to protect goat privacy.</p>
+     *
+     * @return Returns whether the user making this call is a goat.
      */
     public boolean isUserAGoat() {
+        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) {
+            return false;
+        }
         return mContext.getPackageManager()
                 .isPackageAvailable("com.coffeestainstudios.goatsimulator");
     }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a1a11ed..16e5156 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -226,9 +226,10 @@
      * <p>
      * This intent should be launched using
      * {@link Activity#startActivityForResult(Intent, int)} so that the user
-     * knows which app is requesting to clear cache. The returned result will
-     * be {@link Activity#RESULT_OK} if the activity was launched and the user accepted to clear
-     * cache, or {@link Activity#RESULT_CANCELED} otherwise.
+     * knows which app is requesting to clear cache. The returned result will be:
+     * {@link Activity#RESULT_OK} if the activity was launched and all cache was cleared,
+     * {@link OsConstants#EIO} if an error occurred while clearing the cache or
+     * {@link Activity#RESULT_CANCELED} otherwise.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
     @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index e224e84..91b9390 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1121,6 +1121,7 @@
      * @deprecated The Application Cache API is deprecated and this method will
      *             become a no-op on all Android versions once support is
      *             removed in Chromium. Consider using Service Workers instead.
+     *             See https://web.dev/appcache-removal/ for more information.
      */
     public abstract void setAppCacheEnabled(boolean flag);
 
@@ -1136,6 +1137,7 @@
      * @deprecated The Application Cache API is deprecated and this method will
      *             become a no-op on all Android versions once support is
      *             removed in Chromium. Consider using Service Workers instead.
+     *             See https://web.dev/appcache-removal/ for more information.
      */
     public abstract void setAppCachePath(String appCachePath);
 
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/BrightnessSynchronizer.java
index aa23251..42724be 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/BrightnessSynchronizer.java
@@ -84,17 +84,17 @@
      * Converts between the int brightness system and the float brightness system.
      */
     public static float brightnessIntToFloat(Context context, int brightnessInt) {
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        float pmMinBrightness = pm.getBrightnessConstraint(
+        final PowerManager pm = context.getSystemService(PowerManager.class);
+        final float pmMinBrightness = pm.getBrightnessConstraint(
                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-        float pmMaxBrightness = pm.getBrightnessConstraint(
+        final float pmMaxBrightness = pm.getBrightnessConstraint(
                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
-        int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
-                PowerManager.BRIGHTNESS_ON);
-        int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
-                PowerManager.BRIGHTNESS_ON);
+        final int minBrightnessInt = Math.round(brightnessFloatToIntRange(pmMinBrightness,
+                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
+        final int maxBrightnessInt = Math.round(brightnessFloatToIntRange(pmMaxBrightness,
+                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
 
         return brightnessIntToFloat(brightnessInt, minBrightnessInt, maxBrightnessInt,
                 pmMinBrightness, pmMaxBrightness);
@@ -119,34 +119,43 @@
      * Converts between the float brightness system and the int brightness system.
      */
     public static int brightnessFloatToInt(Context context, float brightnessFloat) {
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        float pmMinBrightness = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-        float pmMaxBrightness = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
-        int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
-                PowerManager.BRIGHTNESS_ON);
-        int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1,
-                PowerManager.BRIGHTNESS_ON);
-
-        return brightnessFloatToInt(brightnessFloat, pmMinBrightness, pmMaxBrightness,
-                minBrightnessInt, maxBrightnessInt);
+        return Math.round(brightnessFloatToIntRange(context, brightnessFloat));
     }
 
     /**
-     * Converts between the float brightness system and the int brightness system.
+     * Converts between the float brightness system and the int brightness system, but returns
+     * the converted value as a float within the int-system's range. This method helps with
+     * conversions from one system to the other without losing the floating-point precision.
      */
-    public static int brightnessFloatToInt(float brightnessFloat, float minFloat, float maxFloat,
-            int minInt, int maxInt) {
+    public static float brightnessFloatToIntRange(Context context, float brightnessFloat) {
+        final PowerManager pm = context.getSystemService(PowerManager.class);
+        final float minFloat = pm.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+        final float maxFloat = pm.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+        final float minInt = brightnessFloatToIntRange(minFloat,
+                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
+        final float maxInt = brightnessFloatToIntRange(maxFloat,
+                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
+                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
+        return brightnessFloatToIntRange(brightnessFloat, minFloat, maxFloat, minInt, maxInt);
+    }
+
+    /**
+     * Translates specified value from the float brightness system to the int brightness system,
+     * given the min/max of each range.  Accounts for special values such as OFF and invalid values.
+     * Value returned as a float privimite (to preserve precision), but is a value within the
+     * int-system range.
+     */
+    private static float brightnessFloatToIntRange(float brightnessFloat, float minFloat,
+            float maxFloat, float minInt, float maxInt) {
         if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
             return PowerManager.BRIGHTNESS_OFF;
         } else if (Float.isNaN(brightnessFloat)) {
             return PowerManager.BRIGHTNESS_INVALID;
         } else {
-            return Math.round(MathUtils.constrainedMap((float) minInt, (float) maxInt, minFloat,
-                    maxFloat, brightnessFloat));
+            return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
         }
     }
 
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index cff669e..9950e55 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3506,13 +3506,11 @@
         }
 
         /**
-         * Only expand direct share area if there is a minimum number of shortcuts,
-         * which will help reduce the amount of visible shuffling due to older-style
-         * direct share targets.
+         * Only expand direct share area if there is a minimum number of targets.
          */
         private boolean canExpandDirectShare() {
             int orientation = getResources().getConfiguration().orientation;
-            return mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
+            return mChooserListAdapter.getNumServiceTargetsForExpand() > getMaxTargetsPerRow()
                     && orientation == Configuration.ORIENTATION_PORTRAIT
                     && !isInMultiWindowMode();
         }
@@ -3721,8 +3719,12 @@
 
                 // only expand if we have more than maxTargetsPerRow, and delay that decision
                 // until they start to scroll
-                if (mChooserMultiProfilePagerAdapter.getActiveListAdapter()
-                        .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
+                ChooserListAdapter adapter =
+                        mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+                int validTargets =
+                        mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
+                                : adapter.getSelectableServiceTargetCount();
+                if (validTargets <= maxTargetsPerRow) {
                     mHideDirectShareExpansion = true;
                     return;
                 }
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 14a2d37..f1b7161 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -78,6 +79,7 @@
 
     private static final int MAX_SUGGESTED_APP_TARGETS = 4;
     private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
+    private static final int MAX_SERVICE_TARGET_APP = 8;
 
     static final int MAX_SERVICE_TARGETS = 8;
 
@@ -97,13 +99,14 @@
     private ChooserTargetInfo
             mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
     private int mValidServiceTargetsNum = 0;
+    private int mAvailableServiceTargetsNum = 0;
     private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>>
             mParkingDirectShareTargets = new HashMap<>();
     private final Map<ComponentName, Map<String, Integer>> mChooserTargetScores = new HashMap<>();
     private Set<ComponentName> mPendingChooserTargetService = new HashSet<>();
     private Set<ComponentName> mShortcutComponents = new HashSet<>();
     private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
-    private final List<TargetInfo> mCallerTargets = new ArrayList<>();
+    private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();
 
     private final ChooserActivity.BaseChooserTargetComparator mBaseTargetComparator =
             new ChooserActivity.BaseChooserTargetComparator();
@@ -235,8 +238,9 @@
     }
 
     @Override
-    protected void onBindView(View view, TargetInfo info) {
-        super.onBindView(view, info);
+    protected void onBindView(View view, TargetInfo info, int position) {
+        super.onBindView(view, info, position);
+        if (info == null) return;
 
         // If target is loading, show a special placeholder shape in the label, make unclickable
         final ViewHolder holder = (ViewHolder) view.getTag();
@@ -253,14 +257,32 @@
             holder.text.setBackground(null);
             holder.itemView.setBackground(holder.defaultItemViewBackground);
         }
+
+        if (info instanceof MultiDisplayResolveInfo) {
+            // If the target is grouped show an indicator
+            Drawable bkg = mContext.getDrawable(R.drawable.chooser_group_background);
+            holder.text.setPaddingRelative(0, 0, bkg.getIntrinsicWidth() /* end */, 0);
+            holder.text.setBackground(bkg);
+        } else if (info.isPinned() && getPositionTargetType(position) == TARGET_STANDARD) {
+            // If the target is pinned and in the suggested row show a pinned indicator
+            Drawable bkg = mContext.getDrawable(R.drawable.chooser_pinned_background);
+            holder.text.setPaddingRelative(bkg.getIntrinsicWidth() /* start */, 0, 0, 0);
+            holder.text.setBackground(bkg);
+        } else {
+            holder.text.setBackground(null);
+            holder.text.setPaddingRelative(0, 0, 0, 0);
+        }
     }
 
     void updateAlphabeticalList() {
         mSortedList.clear();
+        List<DisplayResolveInfo> tempList = new ArrayList<>();
+        tempList.addAll(mDisplayList);
+        tempList.addAll(mCallerTargets);
         if (mEnableStackedApps) {
             // Consolidate multiple targets from same app.
             Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
-            for (DisplayResolveInfo info : mDisplayList) {
+            for (DisplayResolveInfo info : tempList) {
                 String packageName = info.getResolvedComponentName().getPackageName();
                 DisplayResolveInfo multiDri = consolidated.get(packageName);
                 if (multiDri == null) {
@@ -277,7 +299,7 @@
             }
             mSortedList.addAll(consolidated.values());
         } else {
-            mSortedList.addAll(mDisplayList);
+            mSortedList.addAll(tempList);
         }
         Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
     }
@@ -329,7 +351,10 @@
         return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
     }
 
-    int getRankedTargetCount() {
+    /**
+     * Fetch ranked app target count
+     */
+    public int getRankedTargetCount() {
         int spacesAvailable =
                 mChooserListCommunicator.getMaxRankedTargets() - getCallerTargetCount();
         return Math.min(spacesAvailable, super.getCount());
@@ -414,6 +439,19 @@
         return null;
     }
 
+    // Check whether {@code dri} should be added into mDisplayList.
+    @Override
+    protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) {
+        // Checks if this info is already listed in callerTargets.
+        for (TargetInfo existingInfo : mCallerTargets) {
+            if (mResolverListCommunicator
+                    .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
+                return false;
+            }
+        }
+        return super.shouldAddResolveInfo(dri);
+    }
+
     /**
      * Fetch surfaced direct share target info
      */
@@ -573,7 +611,13 @@
             Pair<List<ChooserTargetInfo>, Integer> parkingTargetInfoPair =
                     mParkingDirectShareTargets.getOrDefault(origComponentName,
                             new Pair<>(new ArrayList<>(), 0));
-            parkingTargetInfoPair.first.addAll(parkingTargetInfos);
+            for (ChooserTargetInfo target : parkingTargetInfos) {
+                if (!checkDuplicateTarget(target, parkingTargetInfoPair.first)
+                        && !checkDuplicateTarget(target, mServiceTargets)) {
+                    parkingTargetInfoPair.first.add(target);
+                    mAvailableServiceTargetsNum++;
+                }
+            }
             mParkingDirectShareTargets.put(origComponentName, parkingTargetInfoPair);
             rankTargetsWithinComponent(origComponentName);
             if (isShortcutResult) {
@@ -618,7 +662,7 @@
             List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first;
             int insertedNum = parkingTargetsItem.second;
             while (insertedNum < quota && !parkingTargets.isEmpty()) {
-                if (!checkDuplicateTarget(parkingTargets.get(0))) {
+                if (!checkDuplicateTarget(parkingTargets.get(0), mServiceTargets)) {
                     mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0));
                     mValidServiceTargetsNum++;
                     insertedNum++;
@@ -633,9 +677,6 @@
                         + " totalScore=" + totalScore
                         + " quota=" + quota);
             }
-            if (mShortcutComponents.contains(component)) {
-                mNumShortcutResults += insertedNum - parkingTargetsItem.second;
-            }
             mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum));
         }
         if (!shouldWaitPendingService) {
@@ -651,19 +692,15 @@
             return;
         }
         Log.i(TAG, " fillAllServiceTargets");
-        int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets();
-        List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets);
+        List<ComponentName> topComponentNames = getTopComponentNames(MAX_SERVICE_TARGET_APP);
         // Append all remaining targets of top recommended components into direct share row.
         for (ComponentName component : topComponentNames) {
             if (!mParkingDirectShareTargets.containsKey(component)) {
                 continue;
             }
             mParkingDirectShareTargets.get(component).first.stream()
-                    .filter(target -> !checkDuplicateTarget(target))
+                    .filter(target -> !checkDuplicateTarget(target, mServiceTargets))
                     .forEach(target -> {
-                        if (mShortcutComponents.contains(component)) {
-                            mNumShortcutResults++;
-                        }
                         mServiceTargets.add(mValidServiceTargetsNum, target);
                         mValidServiceTargetsNum++;
                     });
@@ -676,28 +713,34 @@
                 .map(pair -> pair.first)
                 .forEach(targets -> {
                     for (ChooserTargetInfo target : targets) {
-                        if (!checkDuplicateTarget(target)) {
+                        if (!checkDuplicateTarget(target, mServiceTargets)) {
                             mServiceTargets.add(mValidServiceTargetsNum, target);
                             mValidServiceTargetsNum++;
-                            mNumShortcutResults++;
                         }
                     }
                 });
         mParkingDirectShareTargets.clear();
     }
 
-    private boolean checkDuplicateTarget(ChooserTargetInfo chooserTargetInfo) {
+    private boolean checkDuplicateTarget(ChooserTargetInfo target,
+            List<ChooserTargetInfo> destination) {
         // Check for duplicates and abort if found
-        for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
-            if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
+        for (ChooserTargetInfo otherTargetInfo : destination) {
+            if (target.isSimilar(otherTargetInfo)) {
                 return true;
             }
         }
         return false;
     }
 
-    int getNumShortcutResults() {
-        return mNumShortcutResults;
+    /**
+     * The return number have to exceed a minimum limit to make direct share area expandable. When
+     * append direct share targets is enabled, return count of all available targets parking in the
+     * memory; otherwise, it is shortcuts count which will help reduce the amount of visible
+     * shuffling due to older-style direct share targets.
+     */
+    int getNumServiceTargetsForExpand() {
+        return mAppendDirectShareEnabled ? mAvailableServiceTargetsNum : mNumShortcutResults;
     }
 
     /**
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 24bf98b..d942e85 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -85,7 +85,7 @@
 
     private int mLastChosenPosition = -1;
     private boolean mFilterLastUsed;
-    private final ResolverListCommunicator mResolverListCommunicator;
+    final ResolverListCommunicator mResolverListCommunicator;
     private Runnable mPostListReadyRunnable;
     private final boolean mIsAudioCaptureDevice;
     private boolean mIsTabLoaded;
@@ -443,17 +443,24 @@
         // TODO(arangelov): Is that UserHandle.USER_CURRENT check okay?
         if (dri != null && dri.getResolveInfo() != null
                 && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) {
-            // Checks if this info is already listed in display.
-            for (DisplayResolveInfo existingInfo : mDisplayList) {
-                if (mResolverListCommunicator
-                        .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
-                    return;
-                }
+            if (shouldAddResolveInfo(dri)) {
+                mDisplayList.add(dri);
             }
-            mDisplayList.add(dri);
         }
     }
 
+    // Check whether {@code dri} should be added into mDisplayList.
+    protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) {
+        // Checks if this info is already listed in display.
+        for (DisplayResolveInfo existingInfo : mDisplayList) {
+            if (mResolverListCommunicator
+                    .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Nullable
     public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
         TargetInfo target = targetInfoForPosition(position, filtered);
@@ -517,7 +524,7 @@
         if (view == null) {
             view = createView(parent);
         }
-        onBindView(view, getItem(position));
+        onBindView(view, getItem(position), position);
         return view;
     }
 
@@ -534,10 +541,10 @@
     }
 
     public final void bindView(int position, View view) {
-        onBindView(view, getItem(position));
+        onBindView(view, getItem(position), position);
     }
 
-    protected void onBindView(View view, TargetInfo info) {
+    protected void onBindView(View view, TargetInfo info, int position) {
         final ViewHolder holder = (ViewHolder) view.getTag();
         if (info == null) {
             holder.icon.setImageDrawable(
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6f33096..d238d0e 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -61,22 +61,6 @@
     public static final String SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS =
             "screenshot_notification_smart_actions_timeout_ms";
 
-    // Flags related to controls
-
-    /**
-     * (boolean) Whether to have split behavior when opening QS
-     */
-    public static final String QS_SPLIT_ENABLED = "qs_split_enabled";
-
-    /**
-     * (int) Open settings panels for WiFi and BT tiles
-     * 0 - default behavior, link to settings
-     * 1 - open panel on long press, click remains the same
-     * 2 - open panel on click, long press remains the same
-     * 3 - use details on long press
-     */
-    public static final String QS_USE_SETTINGS_PANELS = "qs_use_settings_panels";
-
     // Flags related to Smart Suggestions - these are read in SmartReplyConstants.
 
     /** (boolean) Whether to enable smart suggestions in notifications. */
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a420ba6..7b708ef 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -652,6 +652,7 @@
     char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX];
     char extraOptsBuf[PROPERTY_VALUE_MAX];
     char voldDecryptBuf[PROPERTY_VALUE_MAX];
+    char perfettoHprofOptBuf[sizeof("-XX:PerfettoHprof=") + PROPERTY_VALUE_MAX];
     enum {
       kEMDefault,
       kEMIntPortable,
@@ -766,6 +767,16 @@
     addOption("-verbose:gc");
     //addOption("-verbose:class");
 
+    // On Android, we always want to allow loading the PerfettoHprof plugin.
+    // Even with this option set, we will still only actually load the plugin
+    // if we are on a userdebug build or the app is debuggable or profileable.
+    // This is enforced in art/runtime/runtime.cc.
+    //
+    // We want to be able to disable this, because this does not work on host,
+    // and we do not want to enable it in tests.
+    parseRuntimeOption("dalvik.vm.perfetto_hprof", perfettoHprofOptBuf, "-XX:PerfettoHprof=",
+                       "true");
+
     if (primary_zygote) {
         addOption("-Xprimaryzygote");
     }
diff --git a/core/res/res/drawable/chooser_group_background.xml b/core/res/res/drawable/chooser_group_background.xml
new file mode 100644
index 0000000..036028d
--- /dev/null
+++ b/core/res/res/drawable/chooser_group_background.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_chooser_group_arrow"
+          android:gravity="end|center_vertical"
+          android:width="12dp"
+          android:height="12dp"
+          android:start="4dp"
+          android:end="0dp" />
+</layer-list>
diff --git a/core/res/res/drawable/chooser_pinned_background.xml b/core/res/res/drawable/chooser_pinned_background.xml
new file mode 100644
index 0000000..fbbe8c1
--- /dev/null
+++ b/core/res/res/drawable/chooser_pinned_background.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_chooser_pin"
+          android:gravity="start|center_vertical"
+          android:width="12dp"
+          android:height="12dp"
+          android:start="0dp"
+          android:end="4dp" />
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_chooser_group_arrow.xml b/core/res/res/drawable/ic_chooser_group_arrow.xml
new file mode 100644
index 0000000..d42bb97
--- /dev/null
+++ b/core/res/res/drawable/ic_chooser_group_arrow.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="12dp"
+        android:height="12dp"
+        android:viewportWidth="12"
+        android:viewportHeight="12"
+        android:tint="?attr/textColorSecondary">
+    <path
+        android:pathData="M2,4L6,8L10,4L2,4Z"
+        android:fillColor="#FF000000"/>
+</vector>
diff --git a/core/res/res/drawable/ic_chooser_pin.xml b/core/res/res/drawable/ic_chooser_pin.xml
new file mode 100644
index 0000000..47851dcb
--- /dev/null
+++ b/core/res/res/drawable/ic_chooser_pin.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="12dp"
+        android:height="12dp"
+        android:viewportWidth="12"
+        android:viewportHeight="12"
+        android:tint="?attr/textColorSecondary">
+    <path
+        android:pathData="M8.5,2C8.5,1.45 8.055,1 7.5,1L4.5,1C3.95,1 3.5,1.45 3.5,2L3.5,5.5L2.5,7L2.5,8L5.5,8L5.5,10.5L6,11L6.5,10.5L6.5,8L9.5,8L9.5,7L8.5,5.5L8.5,2Z"
+        android:fillColor="#FF000000" />
+</vector>
diff --git a/core/res/res/layout/car_user_switching_dialog.xml b/core/res/res/layout/car_user_switching_dialog.xml
deleted file mode 100644
index d727434..0000000
--- a/core/res/res/layout/car_user_switching_dialog.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fitsSystemWindows="true"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
-
-  <ImageView
-      android:id="@+id/user_loading_avatar"
-      android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
-      android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height"
-      android:layout_centerHorizontal="true"/>
-
-  <TextView android:id="@+id/user_loading"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:layout_marginTop="@dimen/car_padding_4"
-      android:textSize="@dimen/car_body1_size"
-      android:textColor="@color/car_body1"
-      android:layout_below="@id/user_loading_avatar"
-      android:gravity="center"/>
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index fdd965f..50e6f33 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -44,7 +44,7 @@
               android:layout_height="wrap_content"
               android:textAppearance="?attr/textAppearanceSmall"
               android:textColor="?attr/textColorPrimary"
-              android:textSize="14sp"
+              android:textSize="12sp"
               android:gravity="top|center_horizontal"
               android:lines="1"
               android:ellipsize="end" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 003c0da..fe7b0ae 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -44,6 +44,7 @@
         <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
@@ -59,7 +60,6 @@
         <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
     </string-array>
 
     <string translatable="false" name="status_bar_rotate">rotate</string>
@@ -3329,6 +3329,17 @@
     <!-- Controls the size of the back gesture inset. -->
     <dimen name="config_backGestureInset">0dp</dimen>
 
+    <!-- Array of values used in Gesture Navigation settings page to reduce/increase the back
+     gesture's inset size. These values will be multiplied into the default width, read from the
+     gesture navigation overlay package, in order to create 4 different sizes which are selectable
+     via a slider component. -->
+    <array name="config_backGestureInsetScales">
+        <item>0.75</item>
+        <item>1.00</item>
+        <item>1.33</item>
+        <item>1.66</item>
+    </array>
+
     <!-- Controls whether the navbar needs a scrim with
          {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
     <bool name="config_navBarNeedsScrim">true</bool>
@@ -4165,9 +4176,21 @@
      and a second time clipped to the fill level to indicate charge -->
     <bool name="config_batterymeterDualTone">false</bool>
 
-    <!-- The default peak refresh rate for a given device. Change this value if you want to allow
-         for higher refresh rates to be automatically used out of the box -->
-    <integer name="config_defaultPeakRefreshRate">60</integer>
+    <!-- The default refresh rate for a given device. Change this value to set a higher default
+         refresh rate. If the hardware composer on the device supports display modes with a higher
+         refresh rate than the default value specified here, the framework may use those higher
+         refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+         setFrameRate().
+         If a non-zero value is set for config_defaultPeakRefreshRate, then
+         config_defaultRefreshRate may be set to 0, in which case the value set for
+         config_defaultPeakRefreshRate will act as the default frame rate. -->
+    <integer name="config_defaultRefreshRate">60</integer>
+
+    <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
+         the framework from using higher refresh rates, even if display modes with higher refresh
+         rates are available from hardware composer. Only has an effect if the value is
+         non-zero. -->
+    <integer name="config_defaultPeakRefreshRate">0</integer>
 
     <!-- The display uses different gamma curves for different refresh rates. It's hard for panel
          vendor to tune the curves to have exact same brightness for different refresh rate. So
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index bd4c484..2c4f4c8 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -16,14 +16,7 @@
 */
 -->
 <resources>
-    <dimen name="car_fullscreen_user_pod_icon_text_size">64sp</dimen>
-    <dimen name="car_fullscreen_user_pod_width">243dp</dimen>
-    <dimen name="car_fullscreen_user_pod_height">356dp</dimen>
-    <dimen name="car_fullscreen_user_pod_image_avatar_width">96dp</dimen>
-    <dimen name="car_fullscreen_user_pod_image_avatar_height">96dp</dimen>
     <dimen name="car_large_avatar_size">96dp</dimen>
-
-
     <!-- Application Bar -->
     <dimen name="car_app_bar_height">80dp</dimen>
     <!-- Margin -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 54d14f8..233f72ea 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -451,10 +451,14 @@
     <string name="personal_apps_suspension_text">
         Your personal apps are blocked until you turn on your work profile</string>
     <!-- Notification text. This notification lets a user know that their apps will be blocked
-        tomorrow due to a work policy from their IT admin, and that they need to turn on their work
-        profile to prevent the apps from being blocked. [CHAR LIMIT=NONE] -->
-    <string name="personal_apps_suspension_tomorrow_text">
-        Your personal apps will be blocked tomorrow</string>
+        at a particular time due to a work policy from their IT admin, and that they need to turn on
+        their work profile to prevent the apps from being blocked. It also explains for how many
+        days the profile is allowed to be off and this number is at least 3. [CHAR LIMIT=NONE] -->
+    <string name="personal_apps_suspension_soon_text">
+        Personal apps will be blocked on <xliff:g id="date" example="May 29">%1$s</xliff:g> at
+        <xliff:g id="time" example="5:20 PM">%2$s</xliff:g>. Your work profile can\u2019t stay off
+        for more than <xliff:g id="number" example="3">%3$d</xliff:g> days.
+    </string>
     <!-- Title for the button that turns work profile on. To be used in a notification
         [CHAR LIMIT=NONE] -->
     <string name="personal_apps_suspended_turn_profile_on">Turn on work profile</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 52aab77..7744e9e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1200,7 +1200,7 @@
   <java-symbol type="string" name="location_changed_notification_title" />
   <java-symbol type="string" name="location_changed_notification_text" />
   <java-symbol type="string" name="personal_apps_suspension_title" />
-  <java-symbol type="string" name="personal_apps_suspension_tomorrow_text" />
+  <java-symbol type="string" name="personal_apps_suspension_soon_text" />
   <java-symbol type="string" name="personal_apps_suspension_text" />
   <java-symbol type="string" name="personal_apps_suspended_turn_profile_on" />
   <java-symbol type="string" name="notification_work_profile_content_description" />
@@ -2745,6 +2745,10 @@
   <java-symbol type="id" name="chooser_row_text_option" />
   <java-symbol type="dimen" name="chooser_row_text_option_translate" />
   <java-symbol type="dimen" name="chooser_preview_image_max_dimen"/>
+  <java-symbol type="drawable" name="ic_chooser_group_arrow"/>
+  <java-symbol type="drawable" name="chooser_group_background"/>
+  <java-symbol type="drawable" name="ic_chooser_pin"/>
+  <java-symbol type="drawable" name="chooser_pinned_background"/>
   <java-symbol type="integer" name="config_maxShortcutTargetsPerApp" />
   <java-symbol type="layout" name="resolve_grid_item" />
   <java-symbol type="id" name="day_picker_view_pager" />
@@ -2814,6 +2818,7 @@
   <java-symbol type="bool" name="config_navBarNeedsScrim" />
   <java-symbol type="bool" name="config_allowSeamlessRotationDespiteNavBarMoving" />
   <java-symbol type="dimen" name="config_backGestureInset" />
+  <java-symbol type="array" name="config_backGestureInsetScales" />
   <java-symbol type="color" name="system_bar_background_semi_transparent" />
   <java-symbol type="bool" name="config_showGesturalNavigationHints" />
 
@@ -3639,13 +3644,6 @@
   <java-symbol type="color" name="car_card_dark" />
   <java-symbol type="dimen" name="car_body1_size" />
   <java-symbol type="dimen" name="car_padding_4" />
-  <java-symbol type="dimen" name="car_fullscreen_user_pod_icon_text_size" />
-  <java-symbol type="dimen" name="car_fullscreen_user_pod_image_avatar_height" />
-  <java-symbol type="dimen" name="car_fullscreen_user_pod_image_avatar_width" />
-  <java-symbol type="dimen" name="car_large_avatar_size" />
-  <java-symbol type="layout" name="car_user_switching_dialog" />
-  <java-symbol type="id" name="user_loading_avatar" />
-  <java-symbol type="id" name="user_loading" />
   <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" />
 
   <java-symbol type="string" name="battery_saver_description_with_learn_more" />
@@ -3777,6 +3775,7 @@
   <java-symbol type="string" name="bluetooth_airplane_mode_toast" />
 
   <!-- For high refresh rate displays -->
+  <java-symbol type="integer" name="config_defaultRefreshRate" />
   <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
   <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index a93dacf..000e870 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -20,6 +20,7 @@
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -29,6 +30,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertFalse;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
@@ -38,6 +40,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ConfigurationChangeItem;
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.StopActivityItem;
@@ -225,7 +228,7 @@
     }
 
     @Test
-    public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() {
+    public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() {
         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -237,26 +240,91 @@
 
             final ActivityThread activityThread = activity.getActivityThread();
 
-            final Configuration pendingConfig = new Configuration();
-            pendingConfig.orientation = orientation == ORIENTATION_LANDSCAPE
-                    ? ORIENTATION_PORTRAIT
-                    : ORIENTATION_LANDSCAPE;
-            pendingConfig.seq = seq + 2;
+            final Configuration newerConfig = new Configuration();
+            newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE
+                    ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+            newerConfig.seq = seq + 2;
             activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
-                    pendingConfig);
+                    newerConfig);
 
-            final Configuration newConfig = new Configuration();
-            newConfig.orientation = orientation;
-            newConfig.seq = seq + 1;
+            final Configuration olderConfig = new Configuration();
+            olderConfig.orientation = orientation;
+            olderConfig.seq = seq + 1;
 
             activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
-                    newConfig, Display.INVALID_DISPLAY);
+                    olderConfig, INVALID_DISPLAY);
+            assertEquals(numOfConfig, activity.mNumOfConfigChanges);
+            assertEquals(olderConfig.orientation, activity.mConfig.orientation);
+
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    newerConfig, INVALID_DISPLAY);
             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
-            assertEquals(pendingConfig.orientation, activity.mConfig.orientation);
+            assertEquals(newerConfig.orientation, activity.mConfig.orientation);
         });
     }
 
     @Test
+    public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()
+            throws Exception {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+        final ActivityThread activityThread = activity.getActivityThread();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            final Configuration config = new Configuration();
+            config.seq = BASE_SEQ;
+            config.orientation = ORIENTATION_PORTRAIT;
+
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    config, INVALID_DISPLAY);
+        });
+
+        final IApplicationThread appThread = activityThread.getApplicationThread();
+        final int numOfConfig = activity.mNumOfConfigChanges;
+
+        final Configuration processConfigLandscape = new Configuration();
+        processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60));
+        processConfigLandscape.seq = BASE_SEQ + 1;
+
+        final Configuration activityConfigLandscape = new Configuration();
+        activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50));
+        activityConfigLandscape.seq = BASE_SEQ + 2;
+
+        final Configuration processConfigPortrait = new Configuration();
+        processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100));
+        processConfigPortrait.seq = BASE_SEQ + 3;
+
+        final Configuration activityConfigPortrait = new Configuration();
+        activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100));
+        activityConfigPortrait.seq = BASE_SEQ + 4;
+
+        activity.mConfigLatch = new CountDownLatch(1);
+        activity.mTestLatch = new CountDownLatch(1);
+
+        ClientTransaction transaction = newTransaction(activityThread, null);
+        transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape));
+        appThread.scheduleTransaction(transaction);
+
+        transaction = newTransaction(activityThread, activity.getActivityToken());
+        transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape));
+        transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait));
+        transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
+        appThread.scheduleTransaction(transaction);
+
+        activity.mTestLatch.await();
+        activity.mConfigLatch.countDown();
+
+        activity.mConfigLatch = null;
+        activity.mTestLatch = null;
+
+        // Check display metrics, bounds should match the portrait activity bounds.
+        final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
+        assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
+
+        // Ensure that Activity#onConfigurationChanged() is only called once.
+        assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+    }
+
+    @Test
     public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()
             throws Exception {
         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
@@ -268,7 +336,7 @@
             config.orientation = ORIENTATION_PORTRAIT;
 
             activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
-                    config, Display.INVALID_DISPLAY);
+                    config, INVALID_DISPLAY);
         });
 
         final int numOfConfig = activity.mNumOfConfigChanges;
@@ -504,7 +572,7 @@
         config.orientation = ORIENTATION_PORTRAIT;
         config.seq = seq;
         activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
-                Display.INVALID_DISPLAY);
+                INVALID_DISPLAY);
 
         if (activity.mNumOfConfigChanges > numOfConfig) {
             return config.seq;
@@ -514,7 +582,7 @@
         config.orientation = ORIENTATION_LANDSCAPE;
         config.seq = seq + 1;
         activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
-                Display.INVALID_DISPLAY);
+                INVALID_DISPLAY);
 
         return config.seq;
     }
@@ -572,8 +640,12 @@
     }
 
     private static ClientTransaction newTransaction(Activity activity) {
-        final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
-        return ClientTransaction.obtain(appThread, activity.getActivityToken());
+        return newTransaction(activity.getActivityThread(), activity.getActivityToken());
+    }
+
+    private static ClientTransaction newTransaction(ActivityThread activityThread,
+            @Nullable IBinder activityToken) {
+        return ClientTransaction.obtain(activityThread.getApplicationThread(), activityToken);
     }
 
     // Test activity
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 6c23125..4654f63 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -63,7 +63,8 @@
 
     @Test
     public void testRecycleActivityConfigurationChangeItem() {
-        ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain(null);
+        ActivityConfigurationChangeItem emptyItem =
+                ActivityConfigurationChangeItem.obtain(Configuration.EMPTY);
         ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config());
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
@@ -186,7 +187,7 @@
 
     @Test
     public void testRecycleMoveToDisplayItem() {
-        MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, null);
+        MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, Configuration.EMPTY);
         MoveToDisplayItem item = MoveToDisplayItem.obtain(4, config());
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 3f8d9ef..f11adef 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -563,6 +563,11 @@
         }
 
         @Override
+        public void dumpCacheInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
+                throws RemoteException {
+        }
+
+        @Override
         public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
                 String[] strings) throws RemoteException {
         }
diff --git a/core/tests/coretests/src/android/debug/AdbNotificationsTest.java b/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
index 6c187ea..3496e2c 100644
--- a/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
+++ b/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
@@ -56,8 +56,11 @@
         // Verify that the adb notification for usb connections has the correct text.
         assertEquals(title, notification.extras.getCharSequence(Notification.EXTRA_TITLE, ""));
         assertEquals(message, notification.extras.getCharSequence(Notification.EXTRA_TEXT, ""));
-        // Verify the PendingIntent has an explicit intent (b/153356209).
-        assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+        // Verify the PendingIntent has an explicit intent (b/153356209), if there is a
+        // PendingIntent attached.
+        if (notification.contentIntent != null) {
+            assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+        }
     }
 
     @Test
@@ -73,7 +76,10 @@
         // Verify that the adb notification for usb connections has the correct text.
         assertEquals(title, notification.extras.getCharSequence(Notification.EXTRA_TITLE, ""));
         assertEquals(message, notification.extras.getCharSequence(Notification.EXTRA_TEXT, ""));
-        // Verify the PendingIntent has an explicit intent (b/153356209).
-        assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+        // Verify the PendingIntent has an explicit intent (b/153356209), if there is a
+        // PendingIntent attached.
+        if (notification.contentIntent != null) {
+            assertFalse(TextUtils.isEmpty(notification.contentIntent.getIntent().getPackage()));
+        }
     }
 }
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 cb84ff9..dcecb5f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1926,6 +1926,33 @@
                 .check(matches(isDisplayed()));
     }
 
+    @Test
+    public void testDeduplicateCallerTargetRankedTarget() {
+        // Create 4 ranked app targets.
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(4);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        // Create caller target which is duplicate with one of app targets
+        Intent chooserIntent = createChooserIntent(createSendTextIntent(),
+                new Intent[] {new Intent("action.fake")});
+        sOverrides.packageManager = mock(PackageManager.class);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(0,
+                UserHandle.USER_CURRENT);
+        when(sOverrides.packageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(ri);
+        waitForIdle();
+
+        ChooserWrapperActivity activity = mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+
+        // Total 4 targets (1 caller target, 3 ranked targets)
+        assertThat(activity.getAdapter().getCount(), is(4));
+        assertThat(activity.getAdapter().getCallerTargetCount(), is(1));
+        assertThat(activity.getAdapter().getRankedTargetCount(), is(3));
+    }
+
     private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
         Intent chooserIntent = new Intent();
         chooserIntent.setAction(Intent.ACTION_CHOOSER);
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 72827a9..a5a2221 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -16,6 +16,7 @@
   -->
 <permissions>
     <privapp-permissions package="com.android.systemui">
+        <permission name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
         <permission name="android.permission.BATTERY_STATS"/>
         <permission name="android.permission.BIND_APPWIDGET"/>
         <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
diff --git a/data/keyboards/Vendor_28de_Product_1102.kl b/data/keyboards/Vendor_28de_Product_1102.kl
new file mode 100644
index 0000000..150a17a
--- /dev/null
+++ b/data/keyboards/Vendor_28de_Product_1102.kl
@@ -0,0 +1,74 @@
+# 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.
+
+#
+# Steam Controller - Model 1001 - USB
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 307   BUTTON_X
+key 308   BUTTON_Y
+
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+key 312   BUTTON_L2
+key 313   BUTTON_R2
+
+# Triggers.
+axis 0x15 LTRIGGER
+axis 0x14 RTRIGGER
+
+# Left and right stick.
+axis 0x00 X
+axis 0x01 Y
+
+# Right stick / mousepad
+axis 0x03 Z
+axis 0x04 RZ
+
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# Dpad (clicks)
+key 544 DPAD_UP
+key 545 DPAD_DOWN
+key 546 DPAD_LEFT
+key 547 DPAD_RIGHT
+
+# Touching the dpad (light touch without pressing down)
+key 289 BUTTON_1
+# Touching the "right stick" / mousepad (light touch without pressing down)
+key 290 BUTTON_2
+
+# Pressing the large paddle on the back, left (linux BTN_WHEEL / BTN_GEAR_DOWN)
+key 336 BUTTON_3
+# Pressing the large paddle on the back, right (linux BTN_GEAR_UP)
+key 337 BUTTON_4
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Left arrow
+key 314   BUTTON_SELECT
+# Right arrow
+key 315   BUTTON_START
+
+# Steam key
+key 316   BUTTON_MODE
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8ea6883..ea7a556 100755
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1505,7 +1505,7 @@
     public void setSpeakerphoneOn(boolean on){
         final IAudioService service = getService();
         try {
-            service.setSpeakerphoneOn(on);
+            service.setSpeakerphoneOn(mICallBack, on);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 98c2d7f..357c333 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -16,6 +16,7 @@
 package android.media;
 
 import android.util.IntArray;
+
 import com.android.server.LocalServices;
 
 /**
@@ -48,6 +49,18 @@
 
     public abstract void setAccessibilityServiceUids(IntArray uids);
 
+    /**
+     * Called by {@link com.android.server.inputmethod.InputMethodManagerService} to notify the UID
+     * of the currently used {@link android.inputmethodservice.InputMethodService}.
+     *
+     * <p>The caller is expected to take care of any performance implications, e.g. by using a
+     * background thread to call this method.</p>
+     *
+     * @param uid UID of the currently used {@link android.inputmethodservice.InputMethodService}.
+     *            {@link android.os.Process#INVALID_UID} if no IME is active.
+     */
+    public abstract void setInputMethodServiceUid(int uid);
+
     public interface RingerModeDelegate {
         /** Called when external ringer mode is evaluated, returns the new internal ringer mode */
         int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e3b67f8..8137275 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -150,7 +150,7 @@
 
     oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);
 
-    void setSpeakerphoneOn(boolean on);
+    void setSpeakerphoneOn(IBinder cb, boolean on);
 
     boolean isSpeakerphoneOn();
 
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index bbd7399..54675d0 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -321,6 +321,21 @@
     @UnsupportedAppUsage
     private long mNativeObject;
 
+    private String convertMuxerStateCodeToString(int aState) {
+        switch (aState) {
+            case MUXER_STATE_UNINITIALIZED:
+                return "UNINITIALIZED";
+            case MUXER_STATE_INITIALIZED:
+                return "INITIALIZED";
+            case MUXER_STATE_STARTED:
+                return "STARTED";
+            case MUXER_STATE_STOPPED:
+                return "STOPPED";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
     /**
      * Constructor.
      * Creates a media muxer that writes to the specified path.
@@ -397,7 +412,7 @@
             nativeSetOrientationHint(mNativeObject, degrees);
         } else {
             throw new IllegalStateException("Can't set rotation degrees due" +
-                    " to wrong state.");
+                    " to wrong state(" + convertMuxerStateCodeToString(mState) + ")");
         }
     }
 
@@ -432,7 +447,8 @@
         if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) {
             nativeSetLocation(mNativeObject, latitudex10000, longitudex10000);
         } else {
-            throw new IllegalStateException("Can't set location due to wrong state.");
+            throw new IllegalStateException("Can't set location due to wrong state("
+                                             + convertMuxerStateCodeToString(mState) + ")");
         }
     }
 
@@ -451,7 +467,8 @@
             nativeStart(mNativeObject);
             mState = MUXER_STATE_STARTED;
         } else {
-            throw new IllegalStateException("Can't start due to wrong state.");
+            throw new IllegalStateException("Can't start due to wrong state("
+                                             + convertMuxerStateCodeToString(mState) + ")");
         }
     }
 
@@ -462,10 +479,16 @@
      */
     public void stop() {
         if (mState == MUXER_STATE_STARTED) {
-            nativeStop(mNativeObject);
-            mState = MUXER_STATE_STOPPED;
+            try {
+                nativeStop(mNativeObject);
+            } catch (Exception e) {
+                throw e;
+            } finally {
+                mState = MUXER_STATE_STOPPED;
+            }
         } else {
-            throw new IllegalStateException("Can't stop due to wrong state.");
+            throw new IllegalStateException("Can't stop due to wrong state("
+                                             + convertMuxerStateCodeToString(mState) + ")");
         }
     }
 
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 50af60a..a458b16 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.hardware.tv.tuner.V1_0.Constants;
 import android.media.tv.TvInputService;
@@ -55,6 +56,8 @@
 import android.os.Message;
 import android.util.Log;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -208,7 +211,7 @@
     private FrontendInfo mFrontendInfo;
     private Integer mFrontendHandle;
     private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
-
+    private int mUserId;
     private Lnb mLnb;
     private Integer mLnbHandle;
     @Nullable
@@ -232,6 +235,11 @@
             new TunerResourceManager.ResourcesReclaimListener() {
                 @Override
                 public void onReclaimResources() {
+                    if (mFrontend != null) {
+                        FrameworkStatsLog
+                                .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                                    FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
+                    }
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST));
                 }
             };
@@ -261,6 +269,8 @@
                 profile, new HandlerExecutor(mHandler), mResourceListener, clientId);
         mClientId = clientId[0];
 
+        mUserId = ActivityManager.getCurrentUser();
+
         setFrontendInfoList();
         setLnbIds();
     }
@@ -358,6 +368,9 @@
                 TunerUtils.throwExceptionForResult(res, "failed to close frontend");
             }
             mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
             mFrontendHandle = null;
             mFrontend = null;
         }
@@ -557,9 +570,14 @@
      */
     @Result
     public int tune(@NonNull FrontendSettings settings) {
+        Log.d(TAG, "Tune to " + settings.getFrequency());
         mFrontendType = settings.getType();
         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
             mFrontendInfo = null;
+            Log.d(TAG, "Write Stats Log for tuning.");
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
             return nativeTune(settings.getType(), settings);
         }
         return RESULT_UNAVAILABLE;
@@ -602,6 +620,9 @@
             mScanCallback = scanCallback;
             mScanCallbackExecutor = executor;
             mFrontendInfo = null;
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
             return nativeScan(settings.getType(), settings, scanType);
         }
         return RESULT_UNAVAILABLE;
@@ -620,6 +641,10 @@
      */
     @Result
     public int cancelScanning() {
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                    FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
+
         int retVal = nativeStopScan();
         mScanCallback = null;
         mScanCallbackExecutor = null;
@@ -779,12 +804,33 @@
     }
 
     private void onFrontendEvent(int eventType) {
+        Log.d(TAG, "Got event from tuning. Event type: " + eventType);
         if (mOnTunerEventExecutor != null && mOnTuneEventListener != null) {
             mOnTunerEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType));
         }
+
+        Log.d(TAG, "Wrote Stats Log for the events from tuning.");
+        if (eventType == OnTuneEventListener.SIGNAL_LOCKED) {
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
+        } else if (eventType == OnTuneEventListener.SIGNAL_NO_SIGNAL) {
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__NOT_LOCKED);
+        } else if (eventType == OnTuneEventListener.SIGNAL_LOST_LOCK) {
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SIGNAL_LOST);
+        }
     }
 
     private void onLocked() {
+        Log.d(TAG, "Wrote Stats Log for locked event from scanning.");
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                    FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
+
         if (mScanCallbackExecutor != null && mScanCallback != null) {
             mScanCallbackExecutor.execute(() -> mScanCallback.onLocked());
         }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 331477f..43cb25f 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -43,7 +43,7 @@
 
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 
-#include <binder/MemoryHeapBase.h>
+#include <binder/MemoryDealer.h>
 
 #include <cutils/compiler.h>
 
@@ -306,6 +306,7 @@
     CHECK(format->findString("mime", &mime));
     mGraphicOutput = (mime.startsWithIgnoreCase("video/") || mime.startsWithIgnoreCase("image/"))
             && !(flags & CONFIGURE_FLAG_ENCODE);
+    mHasCryptoOrDescrambler = (crypto != nullptr) || (descrambler != nullptr);
 
     return mCodec->configure(
             format, mSurfaceTextureClient, crypto, descrambler, flags);
@@ -1603,14 +1604,13 @@
         ScopedLocalRef<jobject> patternObj{
             env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)};
 
-        CryptoPlugin::Pattern pattern;
         if (patternObj.get() == nullptr) {
-            pattern.mEncryptBlocks = 0;
-            pattern.mSkipBlocks = 0;
+            mPattern.mEncryptBlocks = 0;
+            mPattern.mSkipBlocks = 0;
         } else {
-            pattern.mEncryptBlocks = env->GetIntField(
+            mPattern.mEncryptBlocks = env->GetIntField(
                     patternObj.get(), gFields.patternEncryptBlocksID);
-            pattern.mSkipBlocks = env->GetIntField(
+            mPattern.mSkipBlocks = env->GetIntField(
                     patternObj.get(), gFields.patternSkipBlocksID);
         }
 
@@ -1679,6 +1679,18 @@
                 mIv = env->GetByteArrayElements(mIvObj.get(), nullptr);
             }
         }
+
+    }
+
+    explicit NativeCryptoInfo(jint size)
+        : mIvObj{nullptr, nullptr},
+          mKeyObj{nullptr, nullptr},
+          mMode{CryptoPlugin::kMode_Unencrypted},
+          mPattern{0, 0} {
+        mSubSamples = new CryptoPlugin::SubSample[1];
+        mNumSubSamples = 1;
+        mSubSamples[0].mNumBytesOfClearData = size;
+        mSubSamples[0].mNumBytesOfEncryptedData = 0;
     }
 
     ~NativeCryptoInfo() {
@@ -2128,10 +2140,13 @@
         if (env->GetBooleanField(bufferObj, gLinearBlockInfo.validId)) {
             JMediaCodecLinearBlock *context =
                 (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId);
-            if (cryptoInfoObj != nullptr) {
+            if (codec->hasCryptoOrDescrambler()) {
                 memory = context->toHidlMemory();
+                // TODO: copy if memory is null
+                offset += context->mHidlMemoryOffset;
             } else {
                 buffer = context->toC2Buffer(offset, size);
+                // TODO: copy if buffer is null
             }
         }
         env->MonitorExit(lock.get());
@@ -2141,13 +2156,19 @@
     }
 
     AString errorDetailMsg;
-    if (cryptoInfoObj != nullptr) {
+    if (codec->hasCryptoOrDescrambler()) {
         if (!memory) {
+            ALOGI("queueLinearBlock: no ashmem memory for encrypted content");
             throwExceptionAsNecessary(env, BAD_VALUE);
             return;
         }
-
-        NativeCryptoInfo cryptoInfo{env, cryptoInfoObj};
+        NativeCryptoInfo cryptoInfo = [env, cryptoInfoObj, size]{
+            if (cryptoInfoObj == nullptr) {
+                return NativeCryptoInfo{size};
+            } else {
+                return NativeCryptoInfo{env, cryptoInfoObj};
+            }
+        }();
         err = codec->queueEncryptedLinearBlock(
                 index,
                 memory,
@@ -2162,6 +2183,7 @@
                 &errorDetailMsg);
     } else {
         if (!buffer) {
+            ALOGI("queueLinearBlock: no C2Buffer found");
             throwExceptionAsNecessary(env, BAD_VALUE);
             return;
         }
@@ -2955,13 +2977,13 @@
                 context->mLegacyBuffer->size(),
                 true,  // readOnly
                 true /* clearBuffer */);
-    } else if (context->mHeap) {
+    } else if (context->mMemory) {
         return CreateByteBuffer(
                 env,
-                static_cast<uint8_t *>(context->mHeap->getBase()) + context->mHeap->getOffset(),
-                context->mHeap->getSize(),
+                context->mMemory->unsecurePointer(),
+                context->mMemory->size(),
                 0,
-                context->mHeap->getSize(),
+                context->mMemory->size(),
                 false,  // readOnly
                 true /* clearBuffer */);
     }
@@ -3011,8 +3033,26 @@
         }
     }
     if (hasSecure && !hasNonSecure) {
-        context->mHeap = new MemoryHeapBase(capacity);
-        context->mMemory = hardware::fromHeap(context->mHeap);
+        constexpr size_t kInitialDealerCapacity = 1048576;  // 1MB
+        thread_local sp<MemoryDealer> sDealer = new MemoryDealer(
+                kInitialDealerCapacity, "JNI(1MB)");
+        context->mMemory = sDealer->allocate(capacity);
+        if (context->mMemory == nullptr) {
+            size_t newDealerCapacity = sDealer->getMemoryHeap()->getSize() * 2;
+            while (capacity * 2 > newDealerCapacity) {
+                newDealerCapacity *= 2;
+            }
+            ALOGI("LinearBlock.native_obtain: "
+                  "Dealer capacity increasing from %zuMB to %zuMB",
+                  sDealer->getMemoryHeap()->getSize() / 1048576,
+                  newDealerCapacity / 1048576);
+            sDealer = new MemoryDealer(
+                    newDealerCapacity,
+                    AStringPrintf("JNI(%zuMB)", newDealerCapacity).c_str());
+            context->mMemory = sDealer->allocate(capacity);
+        }
+        context->mHidlMemory = hardware::fromHeap(context->mMemory->getMemory(
+                    &context->mHidlMemoryOffset, &context->mHidlMemorySize));
     } else {
         context->mBlock = MediaCodec::FetchLinearBlock(capacity, names);
         if (!context->mBlock) {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 400ce1b..5c34341 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -162,6 +162,8 @@
 
     void selectAudioPresentation(const int32_t presentationId, const int32_t programId);
 
+    bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; }
+
 protected:
     virtual ~JMediaCodec();
 
@@ -181,6 +183,7 @@
     sp<MediaCodec> mCodec;
     AString mNameAtCreation;
     bool mGraphicOutput{false};
+    bool mHasCryptoOrDescrambler{false};
     std::once_flag mReleaseFlag;
 
     sp<AMessage> mCallbackNotification;
diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h
index 0843834..8f1d2fa 100644
--- a/media/jni/android_media_MediaCodecLinearBlock.h
+++ b/media/jni/android_media_MediaCodecLinearBlock.h
@@ -31,8 +31,10 @@
     std::shared_ptr<C2LinearBlock> mBlock;
     std::shared_ptr<C2WriteView> mReadWriteMapping;
 
-    sp<IMemoryHeap> mHeap;
-    sp<hardware::HidlMemory> mMemory;
+    sp<IMemory> mMemory;
+    sp<hardware::HidlMemory> mHidlMemory;
+    ssize_t mHidlMemoryOffset;
+    size_t mHidlMemorySize;
 
     sp<MediaCodecBuffer> mLegacyBuffer;
 
@@ -56,8 +58,8 @@
     }
 
     sp<hardware::HidlMemory> toHidlMemory() {
-        if (mMemory) {
-            return mMemory;
+        if (mHidlMemory) {
+            return mHidlMemory;
         }
         return nullptr;
     }
@@ -65,4 +67,4 @@
 
 }  // namespace android
 
-#endif  // _ANDROID_MEDIA_MEDIACODECLINEARBLOCK_H_
\ No newline at end of file
+#endif  // _ANDROID_MEDIA_MEDIACODECLINEARBLOCK_H_
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
index 0c1e9a2..262ec76 100644
--- a/media/jni/android_media_MediaMuxer.cpp
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -26,15 +26,11 @@
 #include <unistd.h>
 #include <fcntl.h>
 
-#include <android/api-level.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MediaMuxer.h>
 
-extern "C" int android_get_application_target_sdk_version();
-
 namespace android {
 
 struct fields_t {
@@ -233,31 +229,11 @@
 
     status_t err = muxer->stop();
 
-    if (android_get_application_target_sdk_version() >= __ANDROID_API_R__) {
-        switch (err) {
-            case OK:
-                break;
-            case ERROR_IO: {
-                jniThrowException(env, "java/lang/UncheckedIOException",
-                                  "Muxer stopped unexpectedly");
-                return;
-            }
-            case ERROR_MALFORMED: {
-                jniThrowException(env, "java/io/IOError",
-                                  "Failure of reading or writing operation");
-                return;
-            }
-            default: {
-                jniThrowException(env, "java/lang/IllegalStateException",
-                                  "Failed to stop the muxer");
-                return;
-            }
-        }
-    } else {
-        if (err != OK) {
-            jniThrowException(env, "java/lang/IllegalStateException", "Failed to stop the muxer");
-            return;
-        }
+    if (err != OK) {
+        ALOGE("Error during stop:%d", err);
+        jniThrowException(env, "java/lang/IllegalStateException",
+                    "Error during stop(), muxer would have stopped already");
+        return;
     }
 }
 
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
index 261b9f5..1dd0291 100644
--- a/packages/CarSystemUI/AndroidManifest.xml
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -25,4 +25,6 @@
     <uses-permission android:name="android.car.permission.CAR_ENROLL_TRUST"/>
     <!-- This permission is required to get bluetooth broadcast. -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
+    <!-- This permission is required to check the foreground user id. -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 </manifest>
diff --git a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml
new file mode 100644
index 0000000..0a29424
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml
@@ -0,0 +1,43 @@
+<?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:fitsSystemWindows="true"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:background="@color/car_user_switching_dialog_background_color">
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="vertical"
+      android:layout_gravity="center"
+      android:gravity="center_horizontal">
+    <ImageView
+        android:id="@+id/user_loading_avatar"
+        android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
+        android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height"/>
+
+    <TextView
+        android:id="@+id/user_loading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/car_user_switching_dialog_loading_text_margin_top"
+        android:textSize="@dimen/car_user_switching_dialog_loading_text_font_size"
+        android:textColor="@color/car_user_switching_dialog_loading_text_color"
+        android:layout_below="@id/user_loading_avatar"/>
+  </LinearLayout>
+</FrameLayout>
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index 3542323..2dc499c 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -39,4 +39,9 @@
               android:layout_height="match_parent"
               android:layout="@layout/car_fullscreen_user_switcher"/>
 
+    <ViewStub android:id="@+id/user_switching_dialog_stub"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout="@layout/car_user_switching_dialog"/>
+
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 3e44721..0e84d51 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -55,4 +55,7 @@
     <color name="list_divider_color">@*android:color/car_list_divider_light</color>
     <color name="car_volume_item_divider_color">@*android:color/car_list_divider</color>
     <color name="car_volume_item_background_color">@*android:color/car_card_dark</color>
+
+    <color name="car_user_switching_dialog_background_color">@android:color/black</color>
+    <color name="car_user_switching_dialog_loading_text_color">@*android:color/car_body1</color>
 </resources>
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 67066d7..4bf0fca 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -70,11 +70,13 @@
         to a constant alpha percent value using the initial alpha. -->
     <integer name="config_finalNotificationBackgroundAlpha">100</integer>
 
-    <!-- Car System UI's OverlayViewsMediator-->
+    <!-- Car System UI's OverlayViewsMediator.
+         Whenever a new class is added, make sure to also add that class to OverlayWindowModule. -->
     <string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
         <item>@string/config_notificationPanelViewMediator</item>
         <item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item>
         <item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
+        <item>com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator</item>
     </string-array>
 
     <!--
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index 9014eb1..ed0b485 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -15,6 +15,32 @@
   ~ limitations under the License
   -->
 <resources>
+    <!-- Text size for car -->
+    <dimen name="car_title_size">32sp</dimen>
+    <dimen name="car_title2_size">32sp</dimen>
+    <dimen name="car_headline1_size">45sp</dimen>
+    <dimen name="car_headline2_size">32sp</dimen>
+    <dimen name="car_headline3_size">24sp</dimen>
+    <dimen name="car_headline4_size">20sp</dimen>
+    <dimen name="car_body1_size">32sp</dimen>
+    <dimen name="car_body2_size">28sp</dimen>
+    <dimen name="car_body3_size">26sp</dimen>
+    <dimen name="car_body4_size">24sp</dimen>
+    <!-- car_body5_size is deprecated -->
+    <dimen name="car_body5_size">18sp</dimen>
+    <dimen name="car_label1_size">26sp</dimen>
+    <dimen name="car_label2_size">64sp</dimen>
+    <dimen name="car_action1_size">26sp</dimen>
+    <dimen name="car_action2_size">26sp</dimen>
+    <!-- Paddings -->
+    <dimen name="car_padding_0">4dp</dimen>
+    <dimen name="car_padding_1">8dp</dimen>
+    <dimen name="car_padding_2">16dp</dimen>
+    <dimen name="car_padding_3">24dp</dimen>
+    <dimen name="car_padding_4">32dp</dimen>
+    <dimen name="car_padding_5">64dp</dimen>
+    <dimen name="car_padding_6">96dp</dimen>
+
     <!--
        Note: status bar height and navigation bar heights are defined
        in frameworks/base/core package and thus will have no effect if
@@ -156,4 +182,10 @@
     <dimen name="car_user_switcher_container_height">420dp</dimen>
     <!-- This must be the negative of car_user_switcher_container_height for the animation. -->
     <dimen name="car_user_switcher_container_anim_height">-420dp</dimen>
+
+    <!-- dimensions for car user switching dialog -->
+    <dimen name="car_fullscreen_user_pod_image_avatar_width">96dp</dimen>
+    <dimen name="car_fullscreen_user_pod_image_avatar_height">96dp</dimen>
+    <dimen name="car_user_switching_dialog_loading_text_margin_top">@*android:dimen/car_padding_4</dimen>
+    <dimen name="car_user_switching_dialog_loading_text_font_size">@*android:dimen/car_body1_size</dimen>
 </resources>
diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml
index 9fae4b3..67fd5bb 100644
--- a/packages/CarSystemUI/res/values/strings.xml
+++ b/packages/CarSystemUI/res/values/strings.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- String to represent lowest setting of an HVAC system [CHAR LIMIT=10]-->
     <string name="hvac_min_text">Min</string>
     <!-- String to represent largest setting of an HVAC system [CHAR LIMIT=10]-->
@@ -34,4 +34,8 @@
     <string name="user_add_user_message_setup">When you add a new user, that person needs to set up their space.</string>
     <!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
     <string name="user_add_user_message_update">Any user can update apps for all other users.</string>
+    <!-- Message to inform user that the new user profile is loading. [CHAR LIMIT=20] -->
+    <string name="car_loading_profile">Loading</string>
+    <!-- Message to inform user that the new user profile is loading with additional information on the previous and the next user. [CHAR LIMIT=100] -->
+    <string name="car_loading_profile_developer_message">Loading user (from <xliff:g id="from_user" example="10">%1$d</xliff:g> to <xliff:g id="to_user" example="12">%2$d</xliff:g>)</string>
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index f8729c3..fe2be1d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -30,6 +30,7 @@
 import com.android.systemui.car.keyguard.CarKeyguardViewController;
 import com.android.systemui.car.statusbar.CarStatusBar;
 import com.android.systemui.car.statusbar.CarStatusBarKeyguardViewManager;
+import com.android.systemui.car.statusbar.DozeServiceHost;
 import com.android.systemui.car.statusbar.DummyNotificationShadeWindowController;
 import com.android.systemui.car.volume.CarVolumeDialogComponent;
 import com.android.systemui.dagger.SystemUIRootComponent;
@@ -37,6 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
+import com.android.systemui.doze.DozeHost;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
@@ -174,4 +176,7 @@
     @Binds
     abstract NotificationShadeWindowController bindNotificationShadeWindowController(
             DummyNotificationShadeWindowController notificationShadeWindowController);
+
+    @Binds
+    abstract DozeHost bindDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index a701e9f..ab61b44 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -188,7 +188,7 @@
         stop();
         getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
         mKeyguardStateController.notifyKeyguardDoneFading();
-        mViewMediatorCallback.keyguardGone();
+        mHandler.post(mViewMediatorCallback::keyguardGone);
         notifyKeyguardUpdateMonitor();
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
index 8e11414..5c6472e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.car.navigationbar;
 
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_GESTURES;
 import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
@@ -377,7 +379,7 @@
                             | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                     PixelFormat.TRANSLUCENT);
             lp.setTitle("TopCarNavigationBar");
-            lp.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
+            lp.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES};
             lp.setFitInsetsTypes(0);
             lp.windowAnimations = 0;
             lp.gravity = Gravity.TOP;
@@ -399,7 +401,7 @@
                             | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                     PixelFormat.TRANSLUCENT);
             lp.setTitle("BottomCarNavigationBar");
-            lp.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+            lp.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR, ITYPE_BOTTOM_GESTURES};
             lp.windowAnimations = 0;
             lp.gravity = Gravity.BOTTOM;
             mWindowManager.addView(mBottomNavigationBarWindow, lp);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
index e2eb3fb..d18eadd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
@@ -425,6 +425,11 @@
     }
 
     @Override
+    public void notifyBiometricAuthModeChanged() {
+        // No op.
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         //When executing dump() function simultaneously, we need to serialize them
         //to get mStackScroller's position correctly.
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
new file mode 100644
index 0000000..d23660c
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.statusbar;
+
+import com.android.systemui.doze.DozeHost;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** No-op implementation of {@link DozeHost} for use by car sysui, which does not support dozing. */
+@Singleton
+public class DozeServiceHost implements DozeHost {
+
+    @Inject
+    public DozeServiceHost() {}
+
+    @Override
+    public void addCallback(Callback callback) {
+        // No op.
+    }
+
+    @Override
+    public void removeCallback(Callback callback) {
+        // No op.
+    }
+
+    @Override
+    public void startDozing() {
+        // No op.
+    }
+
+    @Override
+    public void pulseWhileDozing(PulseCallback callback, int reason) {
+        // No op.
+    }
+
+    @Override
+    public void stopDozing() {
+        // No op.
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        // No op.
+    }
+
+    @Override
+    public boolean isPowerSaveActive() {
+        return false;
+    }
+
+    @Override
+    public boolean isPulsingBlocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isProvisioned() {
+        return false;
+    }
+
+    @Override
+    public boolean isBlockingDoze() {
+        return true;
+    }
+
+    @Override
+    public void extendPulse(int reason) {
+        // No op.
+    }
+
+    @Override
+    public void setAnimateWakeup(boolean animateWakeup) {
+        // No op.
+    }
+
+    @Override
+    public void setAnimateScreenOff(boolean animateScreenOff) {
+        // No op.
+    }
+
+    @Override
+    public void onSlpiTap(float x, float y) {
+        // No op.
+    }
+
+    @Override
+    public void setDozeScreenBrightness(int value) {
+        // No op.
+    }
+
+    @Override
+    public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+        // No op.
+    }
+
+    @Override
+    public void cancelGentleSleep() {
+        // No op.
+    }
+
+    @Override
+    public void onIgnoreTouchWhilePulsing(boolean ignore) {
+        // No op.
+    }
+
+    @Override
+    public void stopPulsing() {
+        // No op.
+    }
+
+    @Override
+    public boolean isDozeSuppressed() {
+        return true;
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
index 346c38c..8b399f8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
@@ -34,16 +34,19 @@
     private final StatusBarStateController mStatusBarStateController;
     private final FullScreenUserSwitcherViewController mFullScreenUserSwitcherViewController;
     private final CarKeyguardViewController mCarKeyguardViewController;
+    private final UserSwitchTransitionViewController mUserSwitchTransitionViewController;
 
     @Inject
     public FullscreenUserSwitcherViewMediator(
             StatusBarStateController statusBarStateController,
             CarKeyguardViewController carKeyguardViewController,
+            UserSwitchTransitionViewController userSwitchTransitionViewController,
             FullScreenUserSwitcherViewController fullScreenUserSwitcherViewController) {
 
         mStatusBarStateController = statusBarStateController;
-        mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
         mCarKeyguardViewController = carKeyguardViewController;
+        mUserSwitchTransitionViewController = userSwitchTransitionViewController;
+        mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
     }
 
     @Override
@@ -74,6 +77,11 @@
     private void onUserSelected(UserGridRecyclerView.UserRecord record) {
         if (record.mType != UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
             mCarKeyguardViewController.hideKeyguardToPrepareBouncer();
+            // If guest user, we cannot use record.mInfo.id and should listen to the User lifecycle
+            // event instead.
+            if (record.mType != UserGridRecyclerView.UserRecord.START_GUEST) {
+                mUserSwitchTransitionViewController.handleShow(record.mInfo.id);
+            }
         }
 
         hide();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
new file mode 100644
index 0000000..775ef81
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
@@ -0,0 +1,126 @@
+/*
+ * 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.car.userswitcher;
+
+import static android.car.settings.CarSettings.Global.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.R;
+import com.android.systemui.car.window.OverlayViewController;
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles showing and hiding UserSwitchTransitionView that is mounted to SystemUiOverlayWindow.
+ */
+@Singleton
+public class UserSwitchTransitionViewController extends OverlayViewController {
+    private static final String TAG = "UserSwitchTransitionViewController";
+    private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true";
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Resources mResources;
+    private final UserManager mUserManager;
+
+    @GuardedBy("this")
+    private boolean mShowing;
+    private int mPreviousUserId = UserHandle.USER_NULL;
+
+    @Inject
+    public UserSwitchTransitionViewController(
+            Context context,
+            @Main Handler handler,
+            @Main Resources resources,
+            UserManager userManager,
+            OverlayViewGlobalStateController overlayViewGlobalStateController) {
+
+        super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController);
+
+        mContext = context;
+        mHandler = handler;
+        mResources = resources;
+        mUserManager = userManager;
+    }
+
+    /**
+     * Makes the user switch transition view appear and draws the content inside of it if a user
+     * that is different from the previous user is provided and if the dialog is not already
+     * showing.
+     */
+    void handleShow(@UserIdInt int newUserId) {
+        if (mPreviousUserId == newUserId || mShowing) return;
+        mShowing = true;
+        mHandler.post(() -> {
+            start();
+            populateDialog(mPreviousUserId, newUserId);
+            // next time a new user is selected, this current new user will be the previous user.
+            mPreviousUserId = newUserId;
+        });
+    }
+
+    void handleHide() {
+        if (!mShowing) return;
+        mShowing = false;
+        mHandler.post(this::stop);
+    }
+
+    private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
+        drawUserIcon(newUserId);
+        populateLoadingText(previousUserId, newUserId);
+    }
+
+    private void drawUserIcon(int newUserId) {
+        Bitmap bitmap = mUserManager.getUserIcon(newUserId);
+        if (bitmap != null) {
+            CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(mContext, bitmap);
+            ((ImageView) getLayout().findViewById(R.id.user_loading_avatar))
+                    .setImageDrawable(drawable);
+        }
+    }
+
+    private void populateLoadingText(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
+        TextView msgView = getLayout().findViewById(R.id.user_loading);
+
+        boolean showInfo = ENABLE_DEVELOPER_MESSAGE_TRUE.equals(
+                Settings.Global.getString(mContext.getContentResolver(),
+                        ENABLE_USER_SWITCH_DEVELOPER_MESSAGE));
+
+        if (showInfo && mPreviousUserId != UserHandle.USER_NULL) {
+            msgView.setText(
+                    mResources.getString(R.string.car_loading_profile_developer_message,
+                            previousUserId, newUserId));
+        } else {
+            msgView.setText(mResources.getString(R.string.car_loading_profile));
+        }
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java
new file mode 100644
index 0000000..aea6914
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java
@@ -0,0 +1,85 @@
+/*
+ * 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.car.userswitcher;
+
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.user.CarUserManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.window.OverlayViewMediator;
+
+import javax.inject.Inject;
+
+/**
+ * Registers listeners that subscribe to events that show or hide CarUserSwitchingDialog that is
+ * mounted to SystemUiOverlayWindow.
+ */
+public class UserSwitchTransitionViewMediator implements OverlayViewMediator,
+        CarUserManager.UserSwitchUiCallback {
+    private static final String TAG = "UserSwitchTransitionViewMediator";
+
+    private final CarServiceProvider mCarServiceProvider;
+    private final UserSwitchTransitionViewController mUserSwitchTransitionViewController;
+
+    @Inject
+    public UserSwitchTransitionViewMediator(
+            CarServiceProvider carServiceProvider,
+            UserSwitchTransitionViewController userSwitchTransitionViewController) {
+        mCarServiceProvider = carServiceProvider;
+        mUserSwitchTransitionViewController = userSwitchTransitionViewController;
+    }
+
+    @Override
+    public void registerListeners() {
+        mCarServiceProvider.addListener(car -> {
+            CarUserManager carUserManager =
+                    (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
+
+            if (carUserManager != null) {
+                carUserManager.setUserSwitchUiCallback(this);
+                carUserManager.addListener(Runnable::run, this::handleUserLifecycleEvent);
+            } else {
+                Log.e(TAG, "registerListeners: CarUserManager could not be obtained.");
+            }
+        });
+    }
+
+    @Override
+    public void setupOverlayContentViewControllers() {
+        // no-op.
+    }
+
+    @Override
+    public void showUserSwitchDialog(int userId) {
+        mUserSwitchTransitionViewController.handleShow(userId);
+    }
+
+    @VisibleForTesting
+    void handleUserLifecycleEvent(CarUserManager.UserLifecycleEvent event) {
+        if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING
+                && ActivityManager.getCurrentUser() == event.getUserId()) {
+            mUserSwitchTransitionViewController.handleShow(event.getUserId());
+        }
+
+        if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+            mUserSwitchTransitionViewController.handleHide();
+        }
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java
index ac574ed..3e7b4a2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewMediator.java
@@ -23,6 +23,9 @@
 
     /**
      * Register listeners that could use ContentVisibilityAdjuster to show/hide content.
+     *
+     * Note that we do not unregister listeners because SystemUI components are expected to live
+     * for the lifecycle of the device.
      */
     void registerListeners();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
index 484aa63..5a16efa 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java
@@ -21,6 +21,7 @@
 import com.android.systemui.car.notification.NotificationPanelViewMediator;
 import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
 import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
+import com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator;
 
 import dagger.Binds;
 import dagger.Module;
@@ -67,4 +68,11 @@
     @ClassKey(FullscreenUserSwitcherViewMediator.class)
     public abstract OverlayViewMediator bindFullscreenUserSwitcherViewsMediator(
             FullscreenUserSwitcherViewMediator overlayViewsMediator);
+
+    /** Injects CarUserSwitchingDialogMediator. */
+    @Binds
+    @IntoMap
+    @ClassKey(UserSwitchTransitionViewMediator.class)
+    public abstract OverlayViewMediator bindUserSwitchTransitionViewMediator(
+            UserSwitchTransitionViewMediator userSwitchTransitionViewMediator);
 }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
new file mode 100644
index 0000000..eab381c
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.userswitcher;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.UserManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class UserSwitchTransitionViewControllerTest extends SysuiTestCase {
+    private static final int TEST_USER_1 = 100;
+    private static final int TEST_USER_2 = 110;
+
+    private TestableUserSwitchTransitionViewController mCarUserSwitchingDialogController;
+    private TestableResources mTestableResources;
+    @Mock
+    private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTestableResources = mContext.getOrCreateTestableResources();
+        mCarUserSwitchingDialogController = new TestableUserSwitchTransitionViewController(
+                mContext,
+                Handler.getMain(),
+                mTestableResources.getResources(),
+                (UserManager) mContext.getSystemService(Context.USER_SERVICE),
+                mOverlayViewGlobalStateController
+        );
+
+        mCarUserSwitchingDialogController.inflate((ViewGroup) LayoutInflater.from(mContext).inflate(
+                R.layout.sysui_overlay_window, /* root= */ null));
+    }
+
+    @Test
+    public void onHandleShow_newUserSelected_showsDialog() {
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+
+        verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
+                any());
+    }
+
+    @Test
+    public void onHandleShow_alreadyShowing_ignoresRequest() {
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_2);
+
+        // Verify that the request was processed only once.
+        verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
+                any());
+    }
+
+    @Test
+    public void onHandleShow_sameUserSelected_ignoresRequest() {
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+        mCarUserSwitchingDialogController.handleHide();
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+
+        // Verify that the request was processed only once.
+        verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
+                any());
+    }
+
+    @Test
+    public void onHide_currentlyShowing_hidesDialog() {
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+        mCarUserSwitchingDialogController.handleHide();
+
+        verify(mOverlayViewGlobalStateController).hideView(eq(mCarUserSwitchingDialogController),
+                any());
+    }
+
+    @Test
+    public void onHide_notShowing_ignoresRequest() {
+        mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
+        mCarUserSwitchingDialogController.handleHide();
+        mCarUserSwitchingDialogController.handleHide();
+
+        // Verify that the request was processed only once.
+        verify(mOverlayViewGlobalStateController).hideView(eq(mCarUserSwitchingDialogController),
+                any());
+    }
+
+    private final class TestableUserSwitchTransitionViewController extends
+            UserSwitchTransitionViewController {
+
+        private final Handler mHandler;
+
+        TestableUserSwitchTransitionViewController(Context context, Handler handler,
+                Resources resources, UserManager userManager,
+                OverlayViewGlobalStateController overlayViewGlobalStateController) {
+            super(context, handler, resources, userManager, overlayViewGlobalStateController);
+            mHandler = handler;
+        }
+
+        @Override
+        public void handleShow(int currentUserId) {
+            super.handleShow(currentUserId);
+            waitForIdleSync(mHandler);
+        }
+
+        @Override
+        public void handleHide() {
+            super.handleHide();
+            waitForIdleSync(mHandler);
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
new file mode 100644
index 0000000..a808e2d
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.car.userswitcher;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.user.CarUserManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.car.CarServiceProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class UserSwitchTransitionViewMediatorTest {
+    private static final int TEST_USER = 100;
+
+    private UserSwitchTransitionViewMediator mUserSwitchTransitionViewMediator;
+    @Mock
+    private CarServiceProvider mCarServiceProvider;
+    @Mock
+    private UserSwitchTransitionViewController mUserSwitchTransitionViewController;
+    @Mock
+    private CarUserManager.UserLifecycleEvent mUserLifecycleEvent;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mUserSwitchTransitionViewMediator = new UserSwitchTransitionViewMediator(
+                mCarServiceProvider, mUserSwitchTransitionViewController);
+
+    }
+
+    @Test
+    public void onUserLifecycleEvent_userStarting_callsHandleShow() {
+        when(mUserLifecycleEvent.getEventType()).thenReturn(
+                CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING);
+        when(mUserLifecycleEvent.getUserId()).thenReturn(TEST_USER);
+        mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
+
+        verify(mUserSwitchTransitionViewController).handleShow(TEST_USER);
+    }
+
+    @Test
+    public void onUserLifecycleEvent_userSwitching_callsHandleHide() {
+        when(mUserLifecycleEvent.getEventType()).thenReturn(
+                CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+        mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
+
+        verify(mUserSwitchTransitionViewController).handleHide();
+    }
+}
diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml
index d5faac2..60934b1 100644
--- a/packages/PackageInstaller/res/values-ne/strings.xml
+++ b/packages/PackageInstaller/res/values-ne/strings.xml
@@ -24,8 +24,8 @@
     <string name="installing_app" msgid="1165095864863849422">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> स्थापना गर्दै…"</string>
     <string name="install_done" msgid="5987363587661783896">"एप स्थापना गरियो।"</string>
     <string name="install_confirm_question" msgid="8176284075816604590">"तपाईं यो एप स्थापना गर्न चाहनुहुन्छ?"</string>
-    <string name="install_confirm_question_update" msgid="7942235418781274635">"तपाईं यो पहिलेदेखि नै विद्यमान अनुप्रयोगको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
-    <string name="install_confirm_question_update_system" msgid="4713001702777910263">"तपाईं यो अन्तर्निर्मित अनुप्रयोगको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
+    <string name="install_confirm_question_update" msgid="7942235418781274635">"तपाईं यो पहिलेदेखि नै विद्यमान एपको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
+    <string name="install_confirm_question_update_system" msgid="4713001702777910263">"तपाईं यो अन्तर्निर्मित एपको साटो यसको अद्यावधिक संस्करण स्थापना गर्न चाहनुहुन्छ? तपाईंको विद्यमान डेटा गुम्ने छैन।"</string>
     <string name="install_failed" msgid="5777824004474125469">"एप स्थापना गरिएन।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"यो प्याकेज स्थापना गर्ने क्रममा अवरोध गरियो।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"प्याकेजका रूपमा स्थापना नगरिएको एप विद्यमान प्याकेजसँग मेल खाँदैन।"</string>
@@ -49,16 +49,16 @@
     <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"अनुमति छैन"</string>
     <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"हालका प्रयोगकर्तालाई यो स्थापना रद्द गर्ने कार्य गर्ने अनुमति छैन।"</string>
     <string name="generic_error_dlg_title" msgid="5863195085927067752">"त्रुटि"</string>
-    <string name="generic_error_dlg_text" msgid="5287861443265795232">"अनुप्रयोगको स्थापना रद्द गर्न सकिएन।"</string>
-    <string name="uninstall_application_title" msgid="4045420072401428123">"अनुप्रयोगको स्थापना रद्द गर्नु…"</string>
+    <string name="generic_error_dlg_text" msgid="5287861443265795232">"एपको स्थापना रद्द गर्न सकिएन।"</string>
+    <string name="uninstall_application_title" msgid="4045420072401428123">"एपको स्थापना रद्द गर्नु…"</string>
     <string name="uninstall_update_title" msgid="824411791011583031">"अद्यावधिकको स्थापना रद्द गर्नु…"</string>
-    <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> निम्न अनुप्रयोगको अंश हो:"</string>
-    <string name="uninstall_application_text" msgid="3816830743706143980">"तपाईं यो अनुप्रयोगको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
+    <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> निम्न एपको अंश हो:"</string>
+    <string name="uninstall_application_text" msgid="3816830743706143980">"तपाईं यो एपको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
     <string name="uninstall_application_text_all_users" msgid="575491774380227119">"तपाईं "<b>"सबै"</b>" प्रयोगकर्ताका लागि यो एपको स्थापना रद्द गर्न चाहनुहुन्छ? यन्त्रका "<b>"सबै"</b>" प्रयोगकर्ताहरूबाट उक्त एप र यसको डेटा हटाइने छ।"</string>
-    <string name="uninstall_application_text_user" msgid="498072714173920526">"तपाईं प्रयोगकर्ता <xliff:g id="USERNAME">%1$s</xliff:g> का लागि यो अनुप्रयोगको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
+    <string name="uninstall_application_text_user" msgid="498072714173920526">"तपाईं प्रयोगकर्ता <xliff:g id="USERNAME">%1$s</xliff:g> का लागि यो एपको स्थापना रद्द गर्न चाहनुहुन्छ?"</string>
     <string name="uninstall_update_text" msgid="863648314632448705">"यस एपलाई फ्याक्ट्रीको संस्करणले बदल्ने हो? सबै डेटा हटाइने छ।"</string>
     <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"यस एपलाई फ्याक्ट्रीको संस्करणले बदल्ने हो? सबै डेटा हटाइने छ। यसले यस यन्त्रका कार्य प्रोफाइल भएका लगायत सबै प्रयोगकर्ताहरूमा असर पार्छ।"</string>
-    <string name="uninstall_keep_data" msgid="7002379587465487550">"<xliff:g id="SIZE">%1$s</xliff:g> अनुप्रयोगको डेटा राख्नुहोस्।"</string>
+    <string name="uninstall_keep_data" msgid="7002379587465487550">"<xliff:g id="SIZE">%1$s</xliff:g> एपको डेटा राख्नुहोस्।"</string>
     <string name="uninstalling_notification_channel" msgid="840153394325714653">"चलिरहेका स्थापना रद्द गर्ने कार्यहरू"</string>
     <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"असफल भएका स्थापना रद्द गर्ने कार्यहरू"</string>
     <string name="uninstalling" msgid="8709566347688966845">"स्थापना रद्द गर्दै…"</string>
@@ -67,12 +67,12 @@
     <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> को स्थापना रद्द गरियो"</string>
     <string name="uninstall_failed" msgid="1847750968168364332">"स्थापना रद्द गर्न सकिएन।"</string>
     <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> को स्थापना रद्द गर्ने कार्य असफल भयो।"</string>
-    <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"यन्त्रको सक्रिय प्रशासकीय अनुप्रयोगको स्थापना रद्द गर्न मिल्दैन"</string>
-    <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> को यन्त्रको सक्रिय प्रशासकीय अनुप्रयोगको स्थापना रद्द गर्न मिल्दैन"</string>
-    <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"अन्य प्रयोगकर्ताहरूका लागि यस अनुप्रयोगको स्थापना रद्द गरे पनि केही प्रयोगकर्ता वा प्रोफाइलहरूलाई यसको आवश्यकता पर्दछ"</string>
+    <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"यन्त्रको सक्रिय प्रशासकीय एपको स्थापना रद्द गर्न मिल्दैन"</string>
+    <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> को यन्त्रको सक्रिय प्रशासकीय एपको स्थापना रद्द गर्न मिल्दैन"</string>
+    <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"अन्य प्रयोगकर्ताहरूका लागि यस एपको स्थापना रद्द गरे पनि केही प्रयोगकर्ता वा प्रोफाइलहरूलाई यसको आवश्यकता पर्दछ"</string>
     <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"यो एप तपाईंको प्रोफाइलका लागि आवश्यक छ र यसको स्थापना रद्द गर्न सकिँदैन।"</string>
     <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"यो एप तपाईंको यन्त्रका प्रशासकका लागि आवश्यक छ र यसको स्थापना रद्द गर्न सकिँदैन।"</string>
-    <string name="manage_device_administrators" msgid="3092696419363842816">"यन्त्रका व्यवस्थापकीय अनुप्रयोगको व्यवस्थापन गर्नु…"</string>
+    <string name="manage_device_administrators" msgid="3092696419363842816">"यन्त्रका व्यवस्थापकीय एपको व्यवस्थापन गर्नु…"</string>
     <string name="manage_users" msgid="1243995386982560813">"प्रयोगकर्ताहरूको व्यवस्थापन गर्नुहोस्"</string>
     <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> को स्थापना रद्द गर्न सकिएन।"</string>
     <string name="Parse_error_dlg_text" msgid="1661404001063076789">"प्याकेजलाई पार्स गर्ने क्रममा समस्या भयो।"</string>
@@ -89,7 +89,7 @@
     <string name="anonymous_source_continue" msgid="4375745439457209366">"जारी राख्नुहोस्"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"सेटिङहरू"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"वेयर एपहरूको स्थापना/स्थापना रद्द गर्दै"</string>
-    <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"अनुप्रयोगको स्थापना गरिएको सूचना"</string>
+    <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"एपको स्थापना गरिएको सूचना"</string>
     <string name="notification_installation_success_message" msgid="6450467996056038442">"सफलतापूर्वक स्थापना गरियो"</string>
     <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” सफलतापूर्वक स्थापना गरियो"</string>
 </resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index 1709506..467a60e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -231,11 +231,6 @@
         // Add the new printers, i.e. what is left.
         printers.addAll(discoveredPrinters.values());
 
-        // Do nothing if the printer list is not changed.
-        if (Objects.equals(mPrinters, printers)) {
-            return;
-        }
-
         // Update the list of printers.
         mPrinters.clear();
         mPrinters.addAll(printers);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e42e438..3dbf5a4 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1376,4 +1376,7 @@
 
     <!-- A content description for work profile app [CHAR LIMIT=35] -->
     <string name="accessibility_work_profile_app_description">Work <xliff:g id="app_name" example="Camera">%s</xliff:g></string>
+
+    <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=40] -->
+    <string name="media_transfer_wired_usb_device_name">Wired headphone</string>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index a860ff1..9e8c70f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -381,6 +381,15 @@
         return mInfoMediaManager.getActiveMediaSession();
     }
 
+    /**
+     * Gets the current package name.
+     *
+     * @return current package name
+     */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
     private MediaDevice updateCurrentConnectedDevice() {
         synchronized (mMediaDevicesLock) {
             for (MediaDevice device : mMediaDevices) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 8ea5ff1..9a3ae1b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -61,11 +61,11 @@
         switch (mRouteInfo.getType()) {
             case TYPE_WIRED_HEADSET:
             case TYPE_WIRED_HEADPHONES:
-                name = mContext.getString(R.string.media_transfer_wired_device_name);
-                break;
             case TYPE_USB_DEVICE:
             case TYPE_USB_HEADSET:
             case TYPE_USB_ACCESSORY:
+                name = mContext.getString(R.string.media_transfer_wired_usb_device_name);
+                break;
             case TYPE_DOCK:
             case TYPE_HDMI:
                 name = mRouteInfo.getName();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index cefe690..8968340 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -83,7 +83,16 @@
  * <p>An AccessPoint, which would be more fittingly named "WifiNetwork", is an aggregation of
  * {@link ScanResult ScanResults} along with pertinent metadata (e.g. current connection info,
  * network scores) required to successfully render the network to the user.
+ *
+ * @deprecated WifiTracker/AccessPoint is no longer supported, and will be removed in a future
+ * release. Clients that need a dynamic list of available wifi networks should migrate to one of the
+ * newer tracker classes,
+ * {@link com.android.wifitrackerlib.WifiPickerTracker},
+ * {@link com.android.wifitrackerlib.SavedNetworkTracker},
+ * {@link com.android.wifitrackerlib.NetworkDetailsTracker},
+ * in conjunction with {@link com.android.wifitrackerlib.WifiEntry} to represent each wifi network.
  */
+@Deprecated
 public class AccessPoint implements Comparable<AccessPoint> {
     static final String TAG = "SettingsLib.AccessPoint";
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 586c154..d1cd043 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -77,7 +77,16 @@
 
 /**
  * Tracks saved or available wifi networks and their state.
+ *
+ * @deprecated WifiTracker/AccessPoint is no longer supported, and will be removed in a future
+ * release. Clients that need a dynamic list of available wifi networks should migrate to one of the
+ * newer tracker classes,
+ * {@link com.android.wifitrackerlib.WifiPickerTracker},
+ * {@link com.android.wifitrackerlib.SavedNetworkTracker},
+ * {@link com.android.wifitrackerlib.NetworkDetailsTracker},
+ * in conjunction with {@link com.android.wifitrackerlib.WifiEntry} to represent each wifi network.
  */
+@Deprecated
 public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
     /**
      * Default maximum age in millis of cached scored networks in
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 47f6fe3..421e5c0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -100,12 +100,12 @@
         when(mInfo.getName()).thenReturn(deviceName);
 
         assertThat(mPhoneMediaDevice.getName())
-                .isEqualTo(mContext.getString(R.string.media_transfer_wired_device_name));
+                .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
 
         when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
 
         assertThat(mPhoneMediaDevice.getName())
-                .isEqualTo(deviceName);
+                .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
 
         when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 83d7218..b85c771 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -346,22 +346,6 @@
                   android:excludeFromRecents="true"
                   android:exported="false" />
 
-        <!--
-        The following is used as a no-op/null home activity when
-        no other MAIN/HOME activity is present (e.g., in CSI).
-        -->
-        <activity android:name=".NullHome"
-                  android:excludeFromRecents="true"
-                  android:label=""
-                  android:screenOrientation="nosensor">
-            <!-- The priority here is set to be lower than that for Settings -->
-            <intent-filter android:priority="-1100">
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.HOME" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <receiver
             android:name=".BugreportRequestedReceiver"
             android:permission="android.permission.TRIGGER_SHELL_BUGREPORT">
diff --git a/packages/Shell/res/layout/null_home_finishing_boot.xml b/packages/Shell/res/layout/null_home_finishing_boot.xml
deleted file mode 100644
index 5f9563a..0000000
--- a/packages/Shell/res/layout/null_home_finishing_boot.xml
+++ /dev/null
@@ -1,44 +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"
-    android:background="#80000000"
-    android:forceHasOverlappingRendering="false">
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:layout_gravity="center"
-        android:layout_marginStart="16dp"
-        android:layout_marginEnd="16dp">
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="40sp"
-            android:textColor="?android:attr/textColorPrimary"
-            android:text="@*android:string/android_start_title"/>
-        <ProgressBar
-            style="@android:style/Widget.Material.ProgressBar.Horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="12.75dp"
-            android:colorControlActivated="?android:attr/textColorPrimary"
-            android:indeterminate="true"/>
-    </LinearLayout>
-</FrameLayout>
diff --git a/packages/Shell/src/com/android/shell/NullHome.java b/packages/Shell/src/com/android/shell/NullHome.java
deleted file mode 100644
index bd97561..0000000
--- a/packages/Shell/src/com/android/shell/NullHome.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.shell;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * This covers the fallback case where no launcher is available.
- * Usually Settings.apk has one fallback home activity.
- * Settings.apk, however, is not part of CSI, which needs to be
- * standalone (bootable and testable).
- */
-public class NullHome extends Activity {
-    private static final String TAG = "NullHome";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.i(TAG, "onCreate");
-        setContentView(R.layout.null_home_finishing_boot);
-    }
-
-    protected void onDestroy() {
-        super.onDestroy();
-        Log.i(TAG, "onDestroy");
-    }
-}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c4ea2e9..055b2be 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -155,6 +155,7 @@
     <!-- Screen Recording -->
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
 
     <!-- Assist -->
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -685,6 +686,7 @@
         </activity>
 
         <activity android:name=".controls.management.ControlsEditingActivity"
+                  android:label="@string/controls_menu_edit"
                   android:theme="@style/Theme.ControlsManagement"
                   android:excludeFromRecents="true"
                   android:noHistory="true"
@@ -762,7 +764,8 @@
         <provider
             android:name="com.android.keyguard.clock.ClockOptionsProvider"
             android:authorities="com.android.keyguard.clock"
-            android:exported="true"
+            android:enabled="false"
+            android:exported="false"
             android:grantUriPermissions="true">
         </provider>
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index aeedc16..e246917 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -66,14 +66,6 @@
 
     void destroy();
 
-    /**
-      * return true if the tile supports detail views, and not
-      * only boolean states
-      */
-    default boolean supportsDetailView() {
-        return false;
-    }
-
     CharSequence getTileLabel();
 
     State getState();
diff --git a/packages/SystemUI/res/anim/media_button_state_list_animator.xml b/packages/SystemUI/res/anim/media_button_state_list_animator.xml
new file mode 100644
index 0000000..62ebbaa
--- /dev/null
+++ b/packages/SystemUI/res/anim/media_button_state_list_animator.xml
@@ -0,0 +1,50 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <set>
+            <objectAnimator
+                android:interpolator="@interpolator/control_state"
+                android:duration="50"
+                android:propertyName="scaleX"
+                android:valueTo="0.9"
+                android:valueType="floatType" />
+            <objectAnimator
+                android:interpolator="@interpolator/control_state"
+                android:duration="50"
+                android:propertyName="scaleY"
+                android:valueTo="0.9"
+                android:valueType="floatType" />
+        </set>
+    </item>
+    <item>
+        <set>
+            <objectAnimator
+                android:interpolator="@interpolator/control_state"
+                android:duration="250"
+                android:propertyName="scaleX"
+                android:valueTo="1"
+                android:valueType="floatType" />
+            <objectAnimator
+                android:interpolator="@interpolator/control_state"
+                android:duration="250"
+                android:propertyName="scaleY"
+                android:valueTo="1"
+                android:valueType="floatType" />
+        </set>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
new file mode 100644
index 0000000..b1f4cb7
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/global_screenshot_preview"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/global_screenshot_x_scale"
+    android:layout_gravity="center"
+    android:layout_marginStart="@dimen/screenshot_offset_x"
+    android:layout_marginBottom="@dimen/screenshot_offset_y"
+    android:scaleType="fitStart"
+    android:elevation="@dimen/screenshot_preview_elevation"
+    android:visibility="gone"
+    android:background="@drawable/screenshot_rounded_corners"
+    android:adjustViewBounds="true"
+    android:contentDescription="@string/screenshot_preview_description"
+    app:layout_constraintBottom_toBottomOf="parent"
+    app:layout_constraintStart_toStartOf="parent"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
index 477ec6a..99b9ced 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
@@ -14,57 +14,39 @@
      limitations under the License.
 -->
 
-<!-- RelativeLayouts have an issue enforcing minimum heights, so just
-     work around this for now with LinearLayouts. -->
-<LinearLayout
+<com.android.systemui.globalactions.GlobalActionsItem
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="0dp"
     android:layout_weight="1"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    android:paddingTop="@dimen/global_actions_grid_item_vertical_margin"
-    android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin"
+    android:layout_height="98dp"
+    android:gravity="bottom|center_horizontal"
+    android:orientation="vertical"
+    android:paddingTop="12dp"
+    android:paddingBottom="12dp"
     android:paddingLeft="@dimen/global_actions_grid_item_side_margin"
     android:paddingRight="@dimen/global_actions_grid_item_side_margin"
     android:layout_marginRight="@dimen/control_base_item_margin"
     android:layout_marginLeft="@dimen/control_base_item_margin"
     android:stateListAnimator="@anim/control_state_list_animator"
     android:background="@drawable/control_background">
-    <LinearLayout
-        android:layout_width="@dimen/global_actions_grid_item_width"
-        android:layout_height="@dimen/global_actions_grid_item_height"
-        android:gravity="top|center_horizontal"
-        android:orientation="vertical">
         <ImageView
             android:id="@*android:id/icon"
-            android:layout_width="@dimen/global_actions_grid_item_icon_width"
-            android:layout_height="@dimen/global_actions_grid_item_icon_height"
-            android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
-            android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
-            android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
-            android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_marginBottom="14dp"
             android:scaleType="centerInside"
-            android:tint="@color/control_default_foreground" />
-
+            android:tint="@color/control_primary_text" />
         <TextView
             android:id="@*android:id/message"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:ellipsize="marquee"
+            android:ellipsize="end"
             android:marqueeRepeatLimit="marquee_forever"
-            android:singleLine="true"
+            android:maxLines="2"
+            android:textSize="12sp"
             android:gravity="center"
-            android:textSize="12dp"
-            android:textColor="@color/control_default_foreground"
+            android:textColor="@color/control_primary_text"
+            android:breakStrategy="high_quality"
+            android:hyphenationFrequency="full"
             android:textAppearance="?android:attr/textAppearanceSmall" />
-
-        <TextView
-            android:visibility="gone"
-            android:id="@*android:id/status"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="@color/control_default_foreground"
-            android:textAppearance="?android:attr/textAppearanceSmall" />
-    </LinearLayout>
-</LinearLayout>
+</com.android.systemui.globalactions.GlobalActionsItem>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index e4e9d29..ab4732d 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -46,8 +46,6 @@
   <com.android.systemui.globalactions.MinHeightScrollView
       android:layout_width="match_parent"
       android:layout_height="match_parent"
-      android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
-      android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"
       android:orientation="vertical"
       android:scrollbars="none"
   >
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 1dbb38d..d469e0f 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -71,21 +71,7 @@
         android:elevation="@dimen/screenshot_preview_elevation"
         android:background="@drawable/screenshot_rounded_corners"
         android:adjustViewBounds="true"/>
-    <ImageView
-        android:id="@+id/global_screenshot_preview"
-        android:layout_width="@dimen/global_screenshot_x_scale"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_marginStart="@dimen/screenshot_offset_x"
-        android:layout_marginBottom="@dimen/screenshot_offset_y"
-        android:scaleType="fitEnd"
-        android:elevation="@dimen/screenshot_preview_elevation"
-        android:visibility="gone"
-        android:background="@drawable/screenshot_rounded_corners"
-        android:adjustViewBounds="true"
-        android:contentDescription="@string/screenshot_preview_description"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"/>
+    <include layout="@layout/global_screenshot_preview"/>
     <FrameLayout
         android:id="@+id/global_screenshot_dismiss_button"
         android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
diff --git a/packages/SystemUI/res/layout/global_screenshot_preview.xml b/packages/SystemUI/res/layout/global_screenshot_preview.xml
new file mode 100644
index 0000000..e6295f5
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_screenshot_preview.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2011 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.
+  -->
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/global_screenshot_preview"
+    android:layout_width="@dimen/global_screenshot_x_scale"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:layout_marginStart="@dimen/screenshot_offset_x"
+    android:layout_marginBottom="@dimen/screenshot_offset_y"
+    android:scaleType="fitEnd"
+    android:elevation="@dimen/screenshot_preview_elevation"
+    android:visibility="gone"
+    android:background="@drawable/screenshot_rounded_corners"
+    android:adjustViewBounds="true"
+    android:contentDescription="@string/screenshot_preview_description"
+    app:layout_constraintBottom_toBottomOf="parent"
+    app:layout_constraintStart_toStartOf="parent"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_tile_detail_text.xml b/packages/SystemUI/res/layout/qs_tile_detail_text.xml
deleted file mode 100644
index bcbf826..0000000
--- a/packages/SystemUI/res/layout/qs_tile_detail_text.xml
+++ /dev/null
@@ -1,33 +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.
--->
-
-<!-- use 'dp' instead of 'sp' as we do not want the text to increase
-     if the user scales the font size -->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="bottom|center_horizontal"
-    android:text="..."
-    android:textSize="16dp"
-    android:fontFamily="@*android:string/config_headlineFontFamily"
-    android:singleLine="true"
-    android:visibility="gone"
-    android:paddingBottom="@dimen/qs_tile_detail_padding"
-    android:clickable="false"
-    android:focusable="false" />
-
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a2e11a7..a3d32c1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -314,7 +314,7 @@
     <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
     <dimen name="screenshot_action_container_offset_y">32dp</dimen>
     <dimen name="screenshot_action_container_corner_radius">10dp</dimen>
-    <dimen name="screenshot_action_container_padding_vertical">10dp</dimen>
+    <dimen name="screenshot_action_container_padding_vertical">16dp</dimen>
     <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
     <dimen name="screenshot_action_container_padding_left">96dp</dimen>
     <dimen name="screenshot_action_container_padding_right">8dp</dimen>
@@ -502,7 +502,6 @@
     <dimen name="qs_page_indicator_width">16dp</dimen>
     <dimen name="qs_page_indicator_height">8dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
-    <dimen name="qs_tile_detail_padding">3dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
     <dimen name="qs_tile_divider_height">1dp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 76ca385..09918e7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -169,5 +169,8 @@
     <item type="id" name="screen_recording_options" />
     <item type="id" name="screen_recording_dialog_source_text" />
     <item type="id" name="screen_recording_dialog_source_description" />
+
+    <item type="id" name="accessibility_action_controls_move_before" />
+    <item type="id" name="accessibility_action_controls_move_after" />
 </resources>
 
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index f35f351..b1e91c8 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -33,11 +33,6 @@
     <!-- Maximum number of bubbles we allow in overflow before we dismiss the oldest one. -->
     <integer name="bubbles_max_overflow">16</integer>
 
-    <!-- Ratio of "left" end of status bar that will swipe to QQS. -->
-    <integer name="qqs_split_fraction">3</integer>
-    <!-- Ratio of "right" end of status bar that will swipe to QS. -->
-    <integer name="qs_split_fraction">2</integer>
-
     <integer name="magnification_default_scale">2</integer>
 
     <!-- The position of the volume dialog on the screen.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8c10f61..8bbcfa0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2711,6 +2711,8 @@
     <string name="accessibility_control_change_favorite">favorite</string>
     <!-- a11y action to unfavorite a control. It will read as "Double-tap to unfavorite" in screen readers [CHAR LIMIT=NONE] -->
     <string name="accessibility_control_change_unfavorite">unfavorite</string>
+    <!-- a11y action to move a control to the position specified by the parameter [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_move">Move to position <xliff:g id="number" example="1">%d</xliff:g></string>
 
     <!-- Controls management controls screen default title [CHAR LIMIT=30] -->
     <string name="controls_favorite_default_title">Controls</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b461295..4f42370 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -623,6 +623,7 @@
     <style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small">
         <item name="android:background">@null</item>
         <item name="android:tint">@android:color/white</item>
+        <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
     </style>
 
     <!-- Used to style charging animation AVD animation -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 1369350..5a78c90 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -86,6 +86,8 @@
     // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
     // stack.
     public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
+    // The global actions dialog is showing
+    public static final int SYSUI_STATE_GLOBAL_ACTIONS_SHOWING = 1 << 15;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -102,7 +104,8 @@
             SYSUI_STATE_SEARCH_DISABLED,
             SYSUI_STATE_TRACING_ENABLED,
             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
-            SYSUI_STATE_BUBBLES_EXPANDED
+            SYSUI_STATE_BUBBLES_EXPANDED,
+            SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
     })
     public @interface SystemUiStateFlags {}
 
@@ -119,6 +122,7 @@
         str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0
                 ? "keygrd_occluded" : "");
         str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : "");
+        str.add((flags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0 ? "global_actions" : "");
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : "");
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : "");
         str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
@@ -192,8 +196,9 @@
      * disabled.
      */
     public static boolean isBackGestureDisabled(int sysuiStateFlags) {
-        // Always allow when the bouncer is showing (even on top of the keyguard)
-        if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0) {
+        // Always allow when the bouncer/global actions is showing (even on top of the keyguard)
+        if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
+                || (sysuiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0) {
             return false;
         }
         // Disable when in immersive, or the notifications are interactive
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 8cd89dd..525e989 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 
@@ -61,6 +62,7 @@
 
     private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0;
     private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3);
+    private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
 
     /**
      * This is the default behavior that will be used once the system is up. It will be set once the
@@ -203,6 +205,10 @@
     }
 
     private boolean handlesUnblocked(boolean ignoreThreshold) {
+        if (!isUserSetupComplete()) {
+            return false;
+        }
+
         long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt;
         boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold();
         ComponentName assistantComponent =
@@ -284,6 +290,11 @@
         mShowAndGoEndsAt = 0;
     }
 
+    private boolean isUserSetupComplete() {
+        return Settings.Secure.getInt(
+                mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    }
+
     @VisibleForTesting
     void setInGesturalModeForTest(boolean inGesturalMode) {
         mInGesturalMode = inGesturalMode;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index f044389..f1b401e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -16,7 +16,6 @@
 package com.android.systemui.bubbles;
 
 
-import static android.app.Notification.FLAG_BUBBLE;
 import static android.os.AsyncTask.Status.FINISHED;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -28,7 +27,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
@@ -57,11 +55,6 @@
 class Bubble implements BubbleViewProvider {
     private static final String TAG = "Bubble";
 
-    /**
-     * NotificationEntry associated with the bubble. A null value implies this bubble is loaded
-     * from disk.
-     */
-    @Nullable
     private NotificationEntry mEntry;
     private final String mKey;
     private final String mGroupId;
@@ -102,56 +95,18 @@
     private Bitmap mBadgedImage;
     private int mDotColor;
     private Path mDotPath;
-    private int mFlags;
 
-    /**
-     * Extract GroupId from {@link NotificationEntry}. See also {@link #groupId(ShortcutInfo)}.
-     */
+
     public static String groupId(NotificationEntry entry) {
         UserHandle user = entry.getSbn().getUser();
         return user.getIdentifier() + "|" + entry.getSbn().getPackageName();
     }
 
-    /**
-     * Extract GroupId from {@link ShortcutInfo}. This should match the one generated from
-     * {@link NotificationEntry}. See also {@link #groupId(NotificationEntry)}.
-     */
-    @NonNull
-    public static String groupId(@NonNull final ShortcutInfo shortcutInfo) {
-        return shortcutInfo.getUserId() + "|" + shortcutInfo.getPackage();
-    }
-
-    /**
-     * Generate a unique identifier for this bubble based on given {@link NotificationEntry}. If
-     * {@link ShortcutInfo} was found in the notification entry, the identifier would be generated
-     * from {@link ShortcutInfo} instead. See also {@link #key(ShortcutInfo)}.
-     */
-    @NonNull
-    public static String key(@NonNull final NotificationEntry entry) {
-        final ShortcutInfo shortcutInfo = entry.getRanking().getShortcutInfo();
-        if (shortcutInfo != null) return key(shortcutInfo);
-        return entry.getKey();
-    }
-
-    /**
-     * Generate a unique identifier for this bubble based on given {@link ShortcutInfo}.
-     * See also {@link #key(NotificationEntry)}.
-     */
-    @NonNull
-    public static String key(@NonNull final ShortcutInfo shortcutInfo) {
-        return shortcutInfo.getUserId() + "|" + shortcutInfo.getPackage() + "|"
-                + shortcutInfo.getId();
-    }
-
-    /**
-     * Create a bubble with limited information based on given {@link ShortcutInfo}.
-     * Note: Currently this is only being used when the bubble is persisted to disk.
-     */
+    // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble
     Bubble(ShortcutInfo shortcutInfo) {
         mShortcutInfo = shortcutInfo;
-        mKey = key(shortcutInfo);
-        mGroupId = groupId(shortcutInfo);
-        mFlags = 0;
+        mKey = shortcutInfo.getId();
+        mGroupId = shortcutInfo.getId();
     }
 
     /** Used in tests when no UI is required. */
@@ -159,11 +114,10 @@
     Bubble(NotificationEntry e,
             BubbleController.NotificationSuppressionChangedListener listener) {
         mEntry = e;
-        mKey = key(e);
+        mKey = e.getKey();
         mLastUpdated = e.getSbn().getPostTime();
         mGroupId = groupId(e);
         mSuppressionListener = listener;
-        mFlags = e.getSbn().getNotification().flags;
     }
 
     @Override
@@ -171,26 +125,16 @@
         return mKey;
     }
 
-    @Nullable
     public NotificationEntry getEntry() {
         return mEntry;
     }
 
-    @Nullable
-    public UserHandle getUser() {
-        if (mEntry != null) return mEntry.getSbn().getUser();
-        if (mShortcutInfo != null) return mShortcutInfo.getUserHandle();
-        return null;
-    }
-
     public String getGroupId() {
         return mGroupId;
     }
 
     public String getPackageName() {
-        return mEntry == null
-                ? mShortcutInfo == null ? null : mShortcutInfo.getPackage()
-                : mEntry.getSbn().getPackageName();
+        return mEntry.getSbn().getPackageName();
     }
 
     @Override
@@ -274,8 +218,7 @@
     void inflate(BubbleViewInfoTask.Callback callback,
             Context context,
             BubbleStackView stackView,
-            BubbleIconFactory iconFactory,
-            boolean skipInflation) {
+            BubbleIconFactory iconFactory) {
         if (isBubbleLoading()) {
             mInflationTask.cancel(true /* mayInterruptIfRunning */);
         }
@@ -283,7 +226,6 @@
                 context,
                 stackView,
                 iconFactory,
-                skipInflation,
                 callback);
         if (mInflateSynchronously) {
             mInflationTask.onPostExecute(mInflationTask.doInBackground());
@@ -403,7 +345,6 @@
      * Whether this notification should be shown in the shade.
      */
     boolean showInShade() {
-        if (mEntry == null) return false;
         return !shouldSuppressNotification() || !mEntry.isClearable();
     }
 
@@ -411,8 +352,8 @@
      * Sets whether this notification should be suppressed in the shade.
      */
     void setSuppressNotification(boolean suppressNotification) {
-        if (mEntry == null) return;
         boolean prevShowInShade = showInShade();
+
         Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
         int flags = data.getFlags();
         if (suppressNotification) {
@@ -443,7 +384,6 @@
      */
     @Override
     public boolean showDot() {
-        if (mEntry == null) return false;
         return mShowBubbleUpdateDot
                 && !mEntry.shouldSuppressNotificationDot()
                 && !shouldSuppressNotification();
@@ -453,7 +393,6 @@
      * Whether the flyout for the bubble should be shown.
      */
     boolean showFlyout() {
-        if (mEntry == null) return false;
         return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
                 && !shouldSuppressNotification()
                 && !mEntry.shouldSuppressNotificationList();
@@ -477,13 +416,11 @@
      * is an ongoing bubble.
      */
     boolean isOngoing() {
-        if (mEntry == null) return false;
         int flags = mEntry.getSbn().getNotification().flags;
         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
     }
 
     float getDesiredHeight(Context context) {
-        if (mEntry == null) return 0;
         Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
         boolean useRes = data.getDesiredHeightResId() != 0;
         if (useRes) {
@@ -497,7 +434,6 @@
     }
 
     String getDesiredHeightString() {
-        if (mEntry == null) return String.valueOf(0);
         Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
         boolean useRes = data.getDesiredHeightResId() != 0;
         if (useRes) {
@@ -514,13 +450,11 @@
      * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
      */
     boolean usingShortcutInfo() {
-        return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null
-                || mShortcutInfo != null;
+        return mEntry.getBubbleMetadata().getShortcutId() != null;
     }
 
     @Nullable
     PendingIntent getBubbleIntent() {
-        if (mEntry == null) return null;
         Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
         if (data != null) {
             return data.getIntent();
@@ -528,32 +462,16 @@
         return null;
     }
 
-    Intent getSettingsIntent(final Context context) {
+    Intent getSettingsIntent() {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
-        final int uid = getUid(context);
-        if (uid != -1) {
-            intent.putExtra(Settings.EXTRA_APP_UID, uid);
-        }
+        intent.putExtra(Settings.EXTRA_APP_UID, mEntry.getSbn().getUid());
         intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         return intent;
     }
 
-    private int getUid(final Context context) {
-        if (mEntry != null) return mEntry.getSbn().getUid();
-        final PackageManager pm = context.getPackageManager();
-        if (pm == null) return -1;
-        try {
-            final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0);
-            return info.uid;
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "cannot find uid", e);
-        }
-        return -1;
-    }
-
     private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
         PackageManager pm = context.getPackageManager();
         Resources r;
@@ -575,30 +493,15 @@
     }
 
     private boolean shouldSuppressNotification() {
-        if (mEntry == null) return false;
         return mEntry.getBubbleMetadata() != null
                 && mEntry.getBubbleMetadata().isNotificationSuppressed();
     }
 
     boolean shouldAutoExpand() {
-        if (mEntry == null) return false;
         Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
         return metadata != null && metadata.getAutoExpandBubble();
     }
 
-    public boolean isBubble() {
-        if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0;
-        return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0;
-    }
-
-    public void enable(int option) {
-        mFlags |= option;
-    }
-
-    public void disable(int option) {
-        mFlags &= ~option;
-    }
-
     @Override
     public String toString() {
         return "Bubble{" + mKey + '}';
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 8707d38..a578f33 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -42,7 +42,6 @@
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.INotificationManager;
@@ -243,7 +242,7 @@
          * This can happen when an app cancels a bubbled notification or when the user dismisses a
          * bubble.
          */
-        void removeNotification(@NonNull NotificationEntry entry, int reason);
+        void removeNotification(NotificationEntry entry, int reason);
 
         /**
          * Called when a bubbled notification has changed whether it should be
@@ -259,7 +258,7 @@
          * removes all remnants of the group's summary from the notification pipeline.
          * TODO: (b/145659174) Only old pipeline needs this - delete post-migration.
          */
-        void maybeCancelSummary(@NonNull NotificationEntry entry);
+        void maybeCancelSummary(NotificationEntry entry);
     }
 
     /**
@@ -482,7 +481,7 @@
 
         addNotifCallback(new NotifCallback() {
             @Override
-            public void removeNotification(@NonNull final NotificationEntry entry, int reason) {
+            public void removeNotification(NotificationEntry entry, int reason) {
                 mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
                         reason);
             }
@@ -493,7 +492,7 @@
             }
 
             @Override
-            public void maybeCancelSummary(@NonNull final NotificationEntry entry) {
+            public void maybeCancelSummary(NotificationEntry entry) {
                 // Check if removed bubble has an associated suppressed group summary that needs
                 // to be removed now.
                 final String groupKey = entry.getSbn().getGroupKey();
@@ -702,12 +701,10 @@
         mBubbleIconFactory = new BubbleIconFactory(mContext);
         // Reload each bubble
         for (Bubble b: mBubbleData.getBubbles()) {
-            b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
-                    false /* skipInflation */);
+            b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
         }
         for (Bubble b: mBubbleData.getOverflowBubbles()) {
-            b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
-                    false /* skipInflation */);
+            b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
         }
     }
 
@@ -806,7 +803,7 @@
             if (bubble != null) {
                 mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
             }
-        } else if (bubble.isBubble()) {
+        } else if (bubble.getEntry().isBubble()){
             mBubbleData.setSelectedBubble(bubble);
         }
         mBubbleData.setExpanded(true);
@@ -835,33 +832,10 @@
         updateBubble(notif, suppressFlyout, true /* showInShade */);
     }
 
-    /**
-     * Fills the overflow bubbles by loading them from disk.
-     */
-    void loadOverflowBubblesFromDisk() {
-        if (!mBubbleData.getOverflowBubbles().isEmpty()) {
-            // we don't need to load overflow bubbles from disk if it is already in memory
-            return;
-        }
-        mDataRepository.loadBubbles((bubbles) -> {
-            bubbles.forEach(bubble -> {
-                if (mBubbleData.getBubbles().contains(bubble)) {
-                    // if the bubble is already active, there's no need to push it to overflow
-                    return;
-                }
-                bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble),
-                        mContext, mStackView, mBubbleIconFactory, true /* skipInflation */);
-            });
-            return null;
-        });
-    }
-
     void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
         if (mStackView == null) {
             // Lazy init stack view when a bubble is created
             ensureStackViewCreated();
-            // Lazy load overflow bubbles from disk
-            loadOverflowBubblesFromDisk();
         }
         // If this is an interruptive notif, mark that it's interrupted
         if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
@@ -881,11 +855,11 @@
                             return;
                         }
                         mHandler.post(
-                                () -> removeBubble(bubble.getKey(),
+                                () -> removeBubble(bubble.getEntry(),
                                         BubbleController.DISMISS_INVALID_INTENT));
                     });
                 },
-                mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
+                mContext, mStackView, mBubbleIconFactory);
     }
 
     /**
@@ -897,10 +871,7 @@
      * @param entry the notification to change bubble state for.
      * @param shouldBubble whether the notification should show as a bubble or not.
      */
-    public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) {
-        if (entry == null) {
-            return;
-        }
+    public void onUserChangedBubble(NotificationEntry entry, boolean shouldBubble) {
         NotificationChannel channel = entry.getChannel();
         final String appPkg = entry.getSbn().getPackageName();
         final int appUid = entry.getSbn().getUid();
@@ -939,14 +910,14 @@
     }
 
     /**
-     * Removes the bubble with the given key.
+     * Removes the bubble with the given NotificationEntry.
      * <p>
      * Must be called from the main thread.
      */
     @MainThread
-    void removeBubble(String key, int reason) {
-        if (mBubbleData.hasAnyBubbleWithKey(key)) {
-            mBubbleData.notificationEntryRemoved(key, reason);
+    void removeBubble(NotificationEntry entry, int reason) {
+        if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
+            mBubbleData.notificationEntryRemoved(entry, reason);
         }
     }
 
@@ -962,7 +933,7 @@
                 && canLaunchInActivityView(mContext, entry);
         if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
             // It was previously a bubble but no longer a bubble -- lets remove it
-            removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
+            removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
         } else if (shouldBubble) {
             updateBubble(entry);
         }
@@ -976,10 +947,10 @@
             // Remove any associated bubble children with the summary
             final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
             for (int i = 0; i < bubbleChildren.size(); i++) {
-                removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
+                removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED);
             }
         } else {
-            removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL);
+            removeBubble(entry, DISMISS_NOTIF_CANCEL);
         }
     }
 
@@ -1001,8 +972,7 @@
             rankingMap.getRanking(key, mTmpRanking);
             boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
             if (isActiveBubble && !mTmpRanking.canBubble()) {
-                mBubbleData.notificationEntryRemoved(entry.getKey(),
-                        BubbleController.DISMISS_BLOCKED);
+                mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
             } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
                 entry.setFlagBubble(true);
                 onEntryUpdated(entry);
@@ -1012,15 +982,9 @@
 
     private void setIsBubble(Bubble b, boolean isBubble) {
         if (isBubble) {
-            if (b.getEntry() != null) {
-                b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE;
-            }
-            b.enable(FLAG_BUBBLE);
+            b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE;
         } else {
-            if (b.getEntry() != null) {
-                b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
-            }
-            b.disable(FLAG_BUBBLE);
+            b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
         }
         try {
             mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0);
@@ -1068,27 +1032,23 @@
                         // The bubble is now gone & the notification is hidden from the shade, so
                         // time to actually remove it
                         for (NotifCallback cb : mCallbacks) {
-                            if (bubble.getEntry() != null) {
-                                cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
-                            }
+                            cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
                         }
                     } else {
-                        if (bubble.isBubble() && bubble.showInShade()) {
+                        if (bubble.getEntry().isBubble() && bubble.showInShade()) {
                             setIsBubble(bubble, /* isBubble */ false);
                         }
-                        if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) {
+                        if (bubble.getEntry().getRow() != null) {
                             bubble.getEntry().getRow().updateBubbleButton();
                         }
                     }
 
                 }
-                if (bubble.getEntry() != null) {
-                    final String groupKey = bubble.getEntry().getSbn().getGroupKey();
-                    if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
-                        // Time to potentially remove the summary
-                        for (NotifCallback cb : mCallbacks) {
-                            cb.maybeCancelSummary(bubble.getEntry());
-                        }
+                final String groupKey = bubble.getEntry().getSbn().getGroupKey();
+                if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+                    // Time to potentially remove the summary
+                    for (NotifCallback cb : mCallbacks) {
+                        cb.maybeCancelSummary(bubble.getEntry());
                     }
                 }
             }
@@ -1113,7 +1073,7 @@
 
             if (update.selectionChanged) {
                 mStackView.setSelectedBubble(update.selectedBubble);
-                if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) {
+                if (update.selectedBubble != null) {
                     mNotificationGroupManager.updateSuppression(
                             update.selectedBubble.getEntry());
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 7b323ce..65d5beb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -23,7 +23,6 @@
 
 import static java.util.stream.Collectors.toList;
 
-import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -224,7 +223,7 @@
                             false, /* showInShade */ true);
                     setSelectedBubble(bubble);
                 },
-                mContext, stack, factory, false /* skipInflation */);
+                mContext, stack, factory);
         dispatchPendingChanges();
     }
 
@@ -279,8 +278,7 @@
         }
         mPendingBubbles.remove(bubble); // No longer pending once we're here
         Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
-        suppressFlyout |= bubble.getEntry() == null
-                || !bubble.getEntry().getRanking().visuallyInterruptive();
+        suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive();
 
         if (prevBubble == null) {
             // Create a new bubble
@@ -309,14 +307,11 @@
         dispatchPendingChanges();
     }
 
-    /**
-     * Called when a notification associated with a bubble is removed.
-     */
-    public void notificationEntryRemoved(String key, @DismissReason int reason) {
+    public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
         if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
+            Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
         }
-        doRemove(key, reason);
+        doRemove(entry.getKey(), reason);
         dispatchPendingChanges();
     }
 
@@ -364,7 +359,7 @@
             return bubbleChildren;
         }
         for (Bubble b : mBubbles) {
-            if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) {
+            if (groupKey.equals(b.getEntry().getSbn().getGroupKey())) {
                 bubbleChildren.add(b);
             }
         }
@@ -475,9 +470,7 @@
             Bubble newSelected = mBubbles.get(newIndex);
             setSelectedBubbleInternal(newSelected);
         }
-        if (bubbleToRemove.getEntry() != null) {
-            maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
-        }
+        maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
     }
 
     void overflowBubble(@DismissReason int reason, Bubble bubble) {
@@ -751,8 +744,7 @@
         return true;
     }
 
-    private void maybeSendDeleteIntent(@DismissReason int reason,
-            @NonNull final NotificationEntry entry) {
+    private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) {
         if (reason == BubbleController.DISMISS_USER_GESTURE) {
             Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata();
             PendingIntent deleteIntent = bubbleMetadata != null
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index 6df3132..ba93f41 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -74,10 +74,7 @@
 
     private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> {
         return bubbles.mapNotNull { b ->
-            var shortcutId = b.shortcutInfo?.id
-            if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId
-            if (shortcutId == null) shortcutId = b.entry?.ranking?.shortcutInfo?.id
-            if (shortcutId == null) return@mapNotNull null
+            val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null
             BubbleEntity(userId, b.packageName, shortcutId)
         }
     }
@@ -111,6 +108,7 @@
     /**
      * Load bubbles from disk.
      */
+    // TODO: call this method from BubbleController and update UI
     @SuppressLint("WrongConstant")
     fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0a1a0f7..baf92dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -65,6 +65,7 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
  * Container for the expanded bubble view, handles rendering the caret and settings icon.
@@ -160,7 +161,7 @@
                             // the bubble again so we'll just remove it.
                             Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
                                     + ", " + e.getMessage() + "; removing bubble");
-                            mBubbleController.removeBubble(getBubbleKey(),
+                            mBubbleController.removeBubble(getBubbleEntry(),
                                     BubbleController.DISMISS_INVALID_INTENT);
                         }
                     });
@@ -204,7 +205,7 @@
             }
             if (mBubble != null) {
                 // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
+                post(() -> mBubbleController.removeBubble(mBubble.getEntry(),
                         BubbleController.DISMISS_TASK_FINISHED));
             }
         }
@@ -291,6 +292,10 @@
         return mBubble != null ? mBubble.getKey() : "null";
     }
 
+    private NotificationEntry getBubbleEntry() {
+        return mBubble != null ? mBubble.getEntry() : null;
+    }
+
     void setManageClickListener(OnClickListener manageClickListener) {
         findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index e35b203..88f5eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -49,6 +49,7 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Vibrator;
+import android.service.notification.StatusBarNotification;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.DisplayCutout;
@@ -918,10 +919,10 @@
                     showManageMenu(false /* show */);
                     final Bubble bubble = mBubbleData.getSelectedBubble();
                     if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
-                        final Intent intent = bubble.getSettingsIntent(mContext);
+                        final Intent intent = bubble.getSettingsIntent();
                         collapseStack(() -> {
-
-                            mContext.startActivityAsUser(intent, bubble.getUser());
+                            mContext.startActivityAsUser(
+                                    intent, bubble.getEntry().getSbn().getUser());
                             logBubbleClickEvent(
                                     bubble,
                                     SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
@@ -1178,19 +1179,13 @@
         for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
             final Bubble bubble = mBubbleData.getBubbles().get(i);
             final String appName = bubble.getAppName();
+            final Notification notification = bubble.getEntry().getSbn().getNotification();
+            final CharSequence titleCharSeq =
+                    notification.extras.getCharSequence(Notification.EXTRA_TITLE);
 
-            final CharSequence titleCharSeq;
-            if (bubble.getEntry() == null) {
-                titleCharSeq = null;
-            } else {
-                titleCharSeq = bubble.getEntry().getSbn().getNotification().extras.getCharSequence(
-                        Notification.EXTRA_TITLE);
-            }
-            final String titleStr;
+            String titleStr = getResources().getString(R.string.notification_bubble_title);
             if (titleCharSeq != null) {
                 titleStr = titleCharSeq.toString();
-            } else {
-                titleStr = getResources().getString(R.string.notification_bubble_title);
             }
 
             if (bubble.getIconView() != null) {
@@ -1803,7 +1798,7 @@
     private void dismissBubbleIfExists(@Nullable Bubble bubble) {
         if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
             mBubbleData.notificationEntryRemoved(
-                    bubble.getKey(), BubbleController.DISMISS_USER_GESTURE);
+                    bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         }
     }
 
@@ -2301,12 +2296,18 @@
      * @param action the user interaction enum.
      */
     private void logBubbleClickEvent(Bubble bubble, int action) {
-        bubble.logUIEvent(
+        StatusBarNotification notification = bubble.getEntry().getSbn();
+        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+                notification.getPackageName(),
+                notification.getNotification().getChannelId(),
+                notification.getId(),
+                getBubbleIndex(getExpandedBubble()),
                 getBubbleCount(),
                 action,
                 getNormalizedXPosition(),
                 getNormalizedYPosition(),
-                getBubbleIndex(getExpandedBubble())
-        );
+                bubble.showInShade(),
+                bubble.isOngoing(),
+                false /* isAppForeground (unused) */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 525d5b5..8a57a73 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -37,7 +37,6 @@
 import android.graphics.drawable.Icon;
 import android.os.AsyncTask;
 import android.os.Parcelable;
-import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.Log;
@@ -75,7 +74,6 @@
     private WeakReference<Context> mContext;
     private WeakReference<BubbleStackView> mStackView;
     private BubbleIconFactory mIconFactory;
-    private boolean mSkipInflation;
     private Callback mCallback;
 
     /**
@@ -86,20 +84,17 @@
             Context context,
             BubbleStackView stackView,
             BubbleIconFactory factory,
-            boolean skipInflation,
             Callback c) {
         mBubble = b;
         mContext = new WeakReference<>(context);
         mStackView = new WeakReference<>(stackView);
         mIconFactory = factory;
-        mSkipInflation = skipInflation;
         mCallback = c;
     }
 
     @Override
     protected BubbleViewInfo doInBackground(Void... voids) {
-        return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble,
-                mSkipInflation);
+        return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble);
     }
 
     @Override
@@ -128,36 +123,11 @@
 
         @Nullable
         static BubbleViewInfo populate(Context c, BubbleStackView stackView,
-                BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) {
-            final NotificationEntry entry = b.getEntry();
-            if (entry == null) {
-                // populate from ShortcutInfo when NotificationEntry is not available
-                final ShortcutInfo s = b.getShortcutInfo();
-                return populate(c, stackView, iconFactory, skipInflation || b.isInflated(),
-                        s.getPackage(), s.getUserHandle(), s, null);
-            }
-            final StatusBarNotification sbn = entry.getSbn();
-            final String bubbleShortcutId =  entry.getBubbleMetadata().getShortcutId();
-            final ShortcutInfo si = bubbleShortcutId == null
-                    ? null : entry.getRanking().getShortcutInfo();
-            return populate(
-                    c, stackView, iconFactory, skipInflation || b.isInflated(),
-                    sbn.getPackageName(), sbn.getUser(), si, entry);
-        }
-
-        private static BubbleViewInfo populate(
-                @NonNull final Context c,
-                @NonNull final BubbleStackView stackView,
-                @NonNull final BubbleIconFactory iconFactory,
-                final boolean isInflated,
-                @NonNull final String packageName,
-                @NonNull final UserHandle user,
-                @Nullable final ShortcutInfo shortcutInfo,
-                @Nullable final NotificationEntry entry) {
+                BubbleIconFactory iconFactory, Bubble b) {
             BubbleViewInfo info = new BubbleViewInfo();
 
             // View inflation: only should do this once per bubble
-            if (!isInflated) {
+            if (!b.isInflated()) {
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.imageView = (BadgedImageView) inflater.inflate(
                         R.layout.bubble_view, stackView, false /* attachToRoot */);
@@ -167,8 +137,12 @@
                 info.expandedView.setStackView(stackView);
             }
 
-            if (shortcutInfo != null) {
-                info.shortcutInfo = shortcutInfo;
+            StatusBarNotification sbn = b.getEntry().getSbn();
+            String packageName = sbn.getPackageName();
+
+            String bubbleShortcutId =  b.getEntry().getBubbleMetadata().getShortcutId();
+            if (bubbleShortcutId != null) {
+                info.shortcutInfo = b.getEntry().getRanking().getShortcutInfo();
             }
 
             // App name & app icon
@@ -187,7 +161,7 @@
                     info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
                 }
                 appIcon = pm.getApplicationIcon(packageName);
-                badgedIcon = pm.getUserBadgedIcon(appIcon, user);
+                badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser());
             } catch (PackageManager.NameNotFoundException exception) {
                 // If we can't find package... don't think we should show the bubble.
                 Log.w(TAG, "Unable to find package: " + packageName);
@@ -196,7 +170,7 @@
 
             // Badged bubble image
             Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
-                    entry == null ? null : entry.getBubbleMetadata());
+                    b.getEntry().getBubbleMetadata());
             if (bubbleDrawable == null) {
                 // Default to app icon
                 bubbleDrawable = appIcon;
@@ -222,9 +196,7 @@
                     Color.WHITE, WHITE_SCRIM_ALPHA);
 
             // Flyout
-            if (entry != null) {
-                info.flyoutMessage = extractFlyoutMessage(c, entry);
-            }
+            info.flyoutMessage = extractFlyoutMessage(c, b.getEntry());
             return info;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index 175ed06..00a406e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -48,6 +48,8 @@
 
     private var modified = false
 
+    override val moveHelper = null
+
     override val favorites: List<ControlInfo>
         get() = favoriteIds.mapNotNull { id ->
             val control = controls.firstOrNull { it.control.controlId == id }?.control
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 4b283d6..2f91710 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName
 import android.graphics.Rect
+import android.os.Bundle
 import android.service.controls.Control
 import android.service.controls.DeviceTypes
 import android.view.LayoutInflater
@@ -78,7 +79,7 @@
                         background = parent.context.getDrawable(
                                 R.drawable.control_background_ripple)
                     },
-                    model is FavoritesModel // Indicates that position information is needed
+                    model?.moveHelper // Indicates that position information is needed
                 ) { id, favorite ->
                     model?.changeFavoriteStatus(id, favorite)
                 }
@@ -176,12 +177,14 @@
 
 /**
  * Holder for using with [ControlStatusWrapper] to display names of zones.
+ * @param moveHelper a helper interface to facilitate a11y rearranging. Null indicates no
+ *                   rearranging
  * @param favoriteCallback this callback will be called whenever the favorite state of the
  *                         [Control] this view represents changes.
  */
 internal class ControlHolder(
     view: View,
-    val withPosition: Boolean,
+    val moveHelper: ControlsModel.MoveHelper?,
     val favoriteCallback: ModelFavoriteChanger
 ) : Holder(view) {
     private val favoriteStateDescription =
@@ -197,7 +200,11 @@
         visibility = View.VISIBLE
     }
 
-    private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription)
+    private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
+        this::stateDescription,
+        this::getLayoutPosition,
+        moveHelper
+    )
 
     init {
         ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate)
@@ -207,7 +214,7 @@
     private fun stateDescription(favorite: Boolean): CharSequence? {
         if (!favorite) {
             return notFavoriteStateDescription
-        } else if (!withPosition) {
+        } else if (moveHelper == null) {
             return favoriteStateDescription
         } else {
             val position = layoutPosition + 1
@@ -256,15 +263,67 @@
     }
 }
 
+/**
+ * Accessibility delegate for [ControlHolder].
+ *
+ * Provides the following functionality:
+ * * Sets the state description indicating whether the controls is Favorited or Unfavorited
+ * * Adds the position to the state description if necessary.
+ * * Adds context action for moving (rearranging) a control.
+ *
+ * @param stateRetriever function to determine the state description based on the favorite state
+ * @param positionRetriever function to obtain the position of this control. It only has to be
+ *                          correct in controls that are currently favorites (and therefore can
+ *                          be moved).
+ * @param moveHelper helper interface to determine if a control can be moved and actually move it.
+ */
 private class ControlHolderAccessibilityDelegate(
-    val stateRetriever: (Boolean) -> CharSequence?
+    val stateRetriever: (Boolean) -> CharSequence?,
+    val positionRetriever: () -> Int,
+    val moveHelper: ControlsModel.MoveHelper?
 ) : AccessibilityDelegateCompat() {
 
     var isFavorite = false
 
+    companion object {
+        private val MOVE_BEFORE_ID = R.id.accessibility_action_controls_move_before
+        private val MOVE_AFTER_ID = R.id.accessibility_action_controls_move_after
+    }
+
     override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
         super.onInitializeAccessibilityNodeInfo(host, info)
 
+        info.isContextClickable = false
+        addClickAction(host, info)
+        maybeAddMoveBeforeAction(host, info)
+        maybeAddMoveAfterAction(host, info)
+
+        // Determine the stateDescription based on the holder information
+        info.stateDescription = stateRetriever(isFavorite)
+        // Remove the information at the end indicating row and column.
+        info.setCollectionItemInfo(null)
+
+        info.className = Switch::class.java.name
+    }
+
+    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+        if (super.performAccessibilityAction(host, action, args)) {
+            return true
+        }
+        return when (action) {
+            MOVE_BEFORE_ID -> {
+                moveHelper?.moveBefore(positionRetriever())
+                true
+            }
+            MOVE_AFTER_ID -> {
+                moveHelper?.moveAfter(positionRetriever())
+                true
+            }
+            else -> false
+        }
+    }
+
+    private fun addClickAction(host: View, info: AccessibilityNodeInfoCompat) {
         // Change the text for the double-tap action
         val clickActionString = if (isFavorite) {
             host.context.getString(R.string.accessibility_control_change_unfavorite)
@@ -276,13 +335,30 @@
             // “favorite/unfavorite”
             clickActionString)
         info.addAction(click)
+    }
 
-        // Determine the stateDescription based on the holder information
-        info.stateDescription = stateRetriever(isFavorite)
-        // Remove the information at the end indicating row and column.
-        info.setCollectionItemInfo(null)
+    private fun maybeAddMoveBeforeAction(host: View, info: AccessibilityNodeInfoCompat) {
+        if (moveHelper?.canMoveBefore(positionRetriever()) ?: false) {
+            val newPosition = positionRetriever() + 1 - 1
+            val moveBefore = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+                MOVE_BEFORE_ID,
+                host.context.getString(R.string.accessibility_control_move, newPosition)
+            )
+            info.addAction(moveBefore)
+            info.isContextClickable = true
+        }
+    }
 
-        info.className = Switch::class.java.name
+    private fun maybeAddMoveAfterAction(host: View, info: AccessibilityNodeInfoCompat) {
+        if (moveHelper?.canMoveAfter(positionRetriever()) ?: false) {
+            val newPosition = positionRetriever() + 1 + 1
+            val moveAfter = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+                MOVE_AFTER_ID,
+                host.context.getString(R.string.accessibility_control_move, newPosition)
+            )
+            info.addAction(moveAfter)
+            info.isContextClickable = true
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index 37b6d15..2543953 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -42,6 +42,8 @@
      */
     val elements: List<ElementWrapper>
 
+    val moveHelper: MoveHelper?
+
     /**
      * Change the favorite status of a particular control.
      */
@@ -69,6 +71,34 @@
          */
         fun onFirstChange()
     }
+
+    /**
+     * Interface to facilitate moving controls from an [AccessibilityDelegate].
+     *
+     * All positions should be 0 based.
+     */
+    interface MoveHelper {
+
+        /**
+         * Whether the control in `position` can be moved to the position before it.
+         */
+        fun canMoveBefore(position: Int): Boolean
+
+        /**
+         * Whether the control in `position` can be moved to the position after it.
+         */
+        fun canMoveAfter(position: Int): Boolean
+
+        /**
+         * Move the control in `position` to the position before it.
+         */
+        fun moveBefore(position: Int)
+
+        /**
+         * Move the control in `position` to the position after it.
+         */
+        fun moveAfter(position: Int)
+    }
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
index 411170cb..5242501 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.controls.management
 
 import android.content.ComponentName
+import android.util.Log
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.controls.ControlInterface
@@ -39,9 +40,39 @@
     private val favoritesModelCallback: FavoritesModelCallback
 ) : ControlsModel {
 
+    companion object {
+        private const val TAG = "FavoritesModel"
+    }
+
     private var adapter: RecyclerView.Adapter<*>? = null
     private var modified = false
 
+    override val moveHelper = object : ControlsModel.MoveHelper {
+        override fun canMoveBefore(position: Int): Boolean {
+            return position > 0 && position < dividerPosition
+        }
+
+        override fun canMoveAfter(position: Int): Boolean {
+            return position >= 0 && position < dividerPosition - 1
+        }
+
+        override fun moveBefore(position: Int) {
+            if (!canMoveBefore(position)) {
+                Log.w(TAG, "Cannot move position $position before")
+            } else {
+                onMoveItem(position, position - 1)
+            }
+        }
+
+        override fun moveAfter(position: Int) {
+            if (!canMoveAfter(position)) {
+                Log.w(TAG, "Cannot move position $position after")
+            } else {
+                onMoveItem(position, position + 1)
+            }
+        }
+    }
+
     override fun attachAdapter(adapter: RecyclerView.Adapter<*>) {
         this.adapter = adapter
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 17e4234..f979bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -18,10 +18,15 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
 import android.animation.ValueAnimator
+import android.annotation.ColorRes
 import android.app.Dialog
 import android.content.Context
+import android.content.res.ColorStateList
 import android.graphics.drawable.ClipDrawable
+import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.LayerDrawable
 import android.service.controls.Control
@@ -34,6 +39,7 @@
 import android.service.controls.templates.ToggleRangeTemplate
 import android.service.controls.templates.ToggleTemplate
 import android.util.MathUtils
+import android.util.TypedValue
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
@@ -63,6 +69,8 @@
         private const val UPDATE_DELAY_IN_MILLIS = 3000L
         private const val ALPHA_ENABLED = 255
         private const val ALPHA_DISABLED = 0
+        private const val STATUS_ALPHA_ENABLED = 1f
+        private const val STATUS_ALPHA_DIMMED = 0.45f
         private val FORCE_PANEL_DEVICES = setOf(
             DeviceTypes.TYPE_THERMOSTAT,
             DeviceTypes.TYPE_CAMERA
@@ -94,9 +102,11 @@
     private val toggleBackgroundIntensity: Float = layout.context.resources
             .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1)
     private var stateAnimator: ValueAnimator? = null
+    private var statusAnimator: Animator? = null
     private val baseLayer: GradientDrawable
     val icon: ImageView = layout.requireViewById(R.id.icon)
-    val status: TextView = layout.requireViewById(R.id.status)
+    private val status: TextView = layout.requireViewById(R.id.status)
+    private var nextStatusText: CharSequence = ""
     val title: TextView = layout.requireViewById(R.id.title)
     val subtitle: TextView = layout.requireViewById(R.id.subtitle)
     val context: Context = layout.getContext()
@@ -105,6 +115,7 @@
     var cancelUpdate: Runnable? = null
     var behavior: Behavior? = null
     var lastAction: ControlAction? = null
+    var isLoading = false
     private var lastChallengeDialog: Dialog? = null
     private val onDialogCancel: () -> Unit = { lastChallengeDialog = null }
 
@@ -144,6 +155,7 @@
             })
         }
 
+        isLoading = false
         behavior = bindBehavior(behavior, findBehaviorClass(controlStatus, template, deviceType))
         updateContentDescription()
     }
@@ -189,11 +201,11 @@
         val previousText = status.getText()
 
         cancelUpdate = uiExecutor.executeDelayed({
-                status.setText(previousText)
-                updateContentDescription()
-            }, UPDATE_DELAY_IN_MILLIS)
+            setStatusText(previousText)
+            updateContentDescription()
+        }, UPDATE_DELAY_IN_MILLIS)
 
-        status.setText(tempStatus)
+        setStatusText(tempStatus)
         updateContentDescription()
     }
 
@@ -231,18 +243,50 @@
     }
 
     internal fun applyRenderInfo(enabled: Boolean, offset: Int, animated: Boolean = true) {
-        setEnabled(enabled)
-
         val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)
-
         val fg = context.resources.getColorStateList(ri.foreground, context.theme)
+        val newText = nextStatusText
+        nextStatusText = ""
+        val control = cws.control
+
+        var shouldAnimate = animated
+        if (newText == status.text) {
+            shouldAnimate = false
+        }
+        animateStatusChange(shouldAnimate) {
+            updateStatusRow(enabled, newText, ri.icon, fg, control)
+        }
+
+        animateBackgroundChange(shouldAnimate, enabled, ri.enabledBackground)
+    }
+
+    fun getStatusText() = status.text
+
+    fun setStatusTextSize(textSize: Float) =
+        status.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
+
+    fun setStatusText(text: CharSequence, immediately: Boolean = false) {
+        if (immediately) {
+            status.alpha = STATUS_ALPHA_ENABLED
+            status.text = text
+            nextStatusText = ""
+        } else {
+            nextStatusText = text
+        }
+    }
+
+    private fun animateBackgroundChange(
+        animated: Boolean,
+        enabled: Boolean,
+        @ColorRes bgColor: Int
+    ) {
         val bg = context.resources.getColor(R.color.control_default_background, context.theme)
         var (newClipColor, newAlpha) = if (enabled) {
             // allow color overrides for the enabled state only
             val color = cws.control?.getCustomColor()?.let {
                 val state = intArrayOf(android.R.attr.state_enabled)
                 it.getColorForState(state, it.getDefaultColor())
-            } ?: context.resources.getColor(ri.enabledBackground, context.theme)
+            } ?: context.resources.getColor(bgColor, context.theme)
             listOf(color, ALPHA_ENABLED)
         } else {
             listOf(
@@ -251,21 +295,6 @@
             )
         }
 
-        status.setTextColor(fg)
-
-        cws.control?.getCustomIcon()?.let {
-            // do not tint custom icons, assume the intended icon color is correct
-            icon.imageTintList = null
-            icon.setImageIcon(it)
-        } ?: run {
-            icon.setImageDrawable(ri.icon)
-
-            // do not color app icons
-            if (deviceType != DeviceTypes.TYPE_ROUTINE) {
-                icon.imageTintList = fg
-            }
-        }
-
         (clipLayer.getDrawable() as GradientDrawable).apply {
             val newBaseColor = if (behavior is ToggleRangeBehavior) {
                 ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity)
@@ -303,6 +332,77 @@
         }
     }
 
+    private fun animateStatusChange(animated: Boolean, statusRowUpdater: () -> Unit) {
+        statusAnimator?.cancel()
+
+        if (!animated) {
+            statusRowUpdater.invoke()
+            return
+        }
+
+        if (isLoading) {
+            statusRowUpdater.invoke()
+            statusAnimator = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_DIMMED).apply {
+                repeatMode = ValueAnimator.REVERSE
+                repeatCount = ValueAnimator.INFINITE
+                duration = 500L
+                interpolator = Interpolators.LINEAR
+                startDelay = 900L
+                start()
+            }
+        } else {
+            val fadeOut = ObjectAnimator.ofFloat(status, "alpha", 0f).apply {
+                duration = 200L
+                interpolator = Interpolators.LINEAR
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        statusRowUpdater.invoke()
+                    }
+                })
+            }
+            val fadeIn = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_ENABLED).apply {
+                duration = 200L
+                interpolator = Interpolators.LINEAR
+            }
+            statusAnimator = AnimatorSet().apply {
+                playSequentially(fadeOut, fadeIn)
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        status.alpha = STATUS_ALPHA_ENABLED
+                        statusAnimator = null
+                    }
+                })
+                start()
+            }
+        }
+    }
+
+    private fun updateStatusRow(
+        enabled: Boolean,
+        text: CharSequence,
+        drawable: Drawable,
+        color: ColorStateList,
+        control: Control?
+    ) {
+        setEnabled(enabled)
+
+        status.text = text
+        status.setTextColor(color)
+
+        control?.getCustomIcon()?.let {
+            // do not tint custom icons, assume the intended icon color is correct
+            icon.imageTintList = null
+            icon.setImageIcon(it)
+        } ?: run {
+            icon.setImageDrawable(drawable)
+
+            // do not color app icons
+            if (deviceType != DeviceTypes.TYPE_ROUTINE) {
+                icon.imageTintList = color
+            }
+        }
+    }
+
     private fun setEnabled(enabled: Boolean) {
         status.setEnabled(enabled)
         icon.setEnabled(enabled)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
index 722ade9..0c8e3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
@@ -24,7 +24,7 @@
     }
 
     override fun bind(cws: ControlWithState, colorOffset: Int) {
-        cvh.status.setText(cws.control?.getStatusText() ?: "")
+        cvh.setStatusText(cws.control?.getStatusText() ?: "")
         cvh.applyRenderInfo(false, colorOffset)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index 124df32..ba331f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -97,6 +97,8 @@
 private const val THERMOSTAT_RANGE = DeviceTypes.TYPE_THERMOSTAT * BUCKET_SIZE
 
 private val deviceColorMap = mapOf<Int, Pair<Int, Int>>(
+    (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to
+        Pair(R.color.control_default_foreground, R.color.control_default_background),
     (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to
         Pair(R.color.thermo_heat_foreground, R.color.control_enabled_thermo_heat_background),
     (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to
@@ -108,13 +110,9 @@
 }
 
 private val deviceIconMap = mapOf<Int, IconState>(
-    THERMOSTAT_RANGE to IconState(
-        R.drawable.ic_device_thermostat_off,
-        R.drawable.ic_device_thermostat_on
-    ),
     (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to IconState(
         R.drawable.ic_device_thermostat_off,
-        R.drawable.ic_device_thermostat_on
+        R.drawable.ic_device_thermostat_off
     ),
     (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to IconState(
         R.drawable.ic_device_thermostat_off,
@@ -130,7 +128,7 @@
     ),
     (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_ECO) to IconState(
         R.drawable.ic_device_thermostat_off,
-        R.drawable.ic_device_thermostat_on
+        R.drawable.ic_device_thermostat_off
     ),
     DeviceTypes.TYPE_THERMOSTAT to IconState(
         R.drawable.ic_device_thermostat_off,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index d8dceba..bf3835d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -32,9 +32,12 @@
         val msg = when (status) {
             Control.STATUS_ERROR -> R.string.controls_error_generic
             Control.STATUS_NOT_FOUND -> R.string.controls_error_removed
-            else -> com.android.internal.R.string.loading
+            else -> {
+                cvh.isLoading = true
+                com.android.internal.R.string.loading
+            }
         }
-        cvh.status.setText(cvh.context.getString(msg))
+        cvh.setStatusText(cvh.context.getString(msg))
         cvh.applyRenderInfo(false, colorOffset)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index 2795c7a..a7dc09b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -39,7 +39,7 @@
     override fun bind(cws: ControlWithState, colorOffset: Int) {
         this.control = cws.control!!
 
-        cvh.status.setText(control.getStatusText())
+        cvh.setStatusText(control.getStatusText())
 
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
index c432c09..dc7247c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
@@ -34,7 +34,6 @@
 
     override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-        cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
 
         cvh.layout.setOnClickListener(View.OnClickListener() {
             cvh.controlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
@@ -44,7 +43,7 @@
     override fun bind(cws: ControlWithState, colorOffset: Int) {
         this.control = cws.control!!
 
-        cvh.status.setText(control.getStatusText())
+        cvh.setStatusText(control.getStatusText())
         val controlTemplate = control.getControlTemplate()
         template = when (controlTemplate) {
             is ToggleTemplate -> controlTemplate
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index a09ed09..1f0ca9b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -31,7 +31,6 @@
 import android.service.controls.templates.ToggleRangeTemplate
 import android.util.Log
 import android.util.MathUtils
-import android.util.TypedValue
 import android.view.GestureDetector
 import android.view.GestureDetector.SimpleOnGestureListener
 import android.view.MotionEvent
@@ -39,7 +38,6 @@
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
-import android.widget.TextView
 import com.android.systemui.Interpolators
 import com.android.systemui.R
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
@@ -57,7 +55,6 @@
     lateinit var control: Control
     lateinit var cvh: ControlViewHolder
     lateinit var rangeTemplate: RangeTemplate
-    lateinit var status: TextView
     lateinit var context: Context
     var currentStatusText: CharSequence = ""
     var currentRangeValue: String = ""
@@ -71,10 +68,7 @@
 
     override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-        status = cvh.status
-        context = status.getContext()
-
-        cvh.applyRenderInfo(false /* enabled */, colorOffset, false /* animated */)
+        context = cvh.context
 
         val gestureListener = ToggleRangeGestureListener(cvh.layout)
         val gestureDetector = GestureDetector(context, gestureListener)
@@ -131,7 +125,6 @@
         this.colorOffset = colorOffset
 
         currentStatusText = control.getStatusText()
-        status.setText(currentStatusText)
 
         // ControlViewHolder sets a long click listener, but we want to handle touch in
         // here instead, otherwise we'll have state conflicts.
@@ -222,7 +215,7 @@
     }
 
     fun beginUpdateRange() {
-        status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
+        cvh.setStatusTextSize(context.getResources()
                 .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat())
     }
 
@@ -261,14 +254,13 @@
             val newValue = levelToRangeValue(newLevel)
             currentRangeValue = format(rangeTemplate.getFormatString().toString(),
                     DEFAULT_FORMAT, newValue)
-            val text = if (isDragging) {
-                currentRangeValue
+            if (isDragging) {
+                cvh.setStatusText(currentRangeValue, /* immediately */ true)
             } else {
-                "$currentStatusText $currentRangeValue"
+                cvh.setStatusText("$currentStatusText $currentRangeValue")
             }
-            status.setText(text)
         } else {
-            status.setText(currentStatusText)
+            cvh.setStatusText(currentStatusText)
         }
     }
 
@@ -296,9 +288,9 @@
     }
 
     fun endUpdateRange() {
-        status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
+        cvh.setStatusTextSize(context.getResources()
                 .getDimensionPixelSize(R.dimen.control_status_normal).toFloat())
-        status.setText("$currentStatusText $currentRangeValue")
+        cvh.setStatusText("$currentStatusText $currentRangeValue", /* immediately */ true)
         cvh.action(FloatAction(rangeTemplate.getTemplateId(),
             findNearestStep(levelToRangeValue(clipLayer.getLevel()))))
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
index 8ce2e61..48f9458 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
@@ -23,6 +23,7 @@
 import android.service.controls.templates.ControlTemplate
 
 import com.android.systemui.R
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
 
 /**
@@ -37,7 +38,6 @@
 
     override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-        cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
 
         cvh.layout.setOnClickListener(View.OnClickListener() {
             cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control)
@@ -46,13 +46,14 @@
 
     override fun bind(cws: ControlWithState, colorOffset: Int) {
         this.control = cws.control!!
-        cvh.status.setText(control.getStatusText())
+        cvh.setStatusText(control.getStatusText())
         template = control.getControlTemplate()
 
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
-        clipLayer.setLevel(MIN_LEVEL)
 
-        cvh.applyRenderInfo(false, colorOffset)
+        val enabled = if (colorOffset > 0) true else false
+        clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL)
+        cvh.applyRenderInfo(enabled, colorOffset)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 82ccb17..e2a6d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -21,7 +21,6 @@
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
 import com.android.systemui.controls.dagger.ControlsModule;
-import com.android.systemui.doze.DozeHost;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.globalactions.GlobalActionsImpl;
 import com.android.systemui.plugins.ActivityStarter;
@@ -38,7 +37,6 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
-import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -259,11 +257,6 @@
     /**
      */
     @Binds
-    public abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
-
-    /**
-     */
-    @Binds
     public abstract VolumeComponent provideVolumeComponent(
             VolumeDialogComponent volumeDialogComponent);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 6d1bf72..3bb953a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -31,6 +31,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
+import com.android.systemui.doze.DozeHost;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
@@ -43,6 +44,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
@@ -148,4 +150,7 @@
     @Binds
     abstract KeyguardViewController bindKeyguardViewController(
             StatusBarKeyguardViewManager statusBarKeyguardViewManager);
+
+    @Binds
+    abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 8117bbb..5e36704 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,7 +33,6 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximitySensor;
@@ -59,7 +58,7 @@
     private final Handler mHandler;
     private final BiometricUnlockController mBiometricUnlockController;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final DozeServiceHost mDozeServiceHost;
+    private final DozeHost mDozeHost;
 
     @Inject
     public DozeFactory(FalsingManager falsingManager, DozeLog dozeLog,
@@ -70,7 +69,7 @@
             ProximitySensor proximitySensor,
             DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
             BiometricUnlockController biometricUnlockController,
-            BroadcastDispatcher broadcastDispatcher, DozeServiceHost dozeServiceHost) {
+            BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) {
         mFalsingManager = falsingManager;
         mDozeLog = dozeLog;
         mDozeParameters = dozeParameters;
@@ -86,7 +85,7 @@
         mHandler = handler;
         mBiometricUnlockController = biometricUnlockController;
         mBroadcastDispatcher = broadcastDispatcher;
-        mDozeServiceHost = dozeServiceHost;
+        mDozeHost = dozeHost;
     }
 
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
@@ -95,7 +94,7 @@
         WakeLock wakeLock = mDelayedWakeLockBuilder.setHandler(mHandler).setTag("Doze").build();
 
         DozeMachine.Service wrappedService = dozeService;
-        wrappedService = new DozeBrightnessHostForwarder(wrappedService, mDozeServiceHost);
+        wrappedService = new DozeBrightnessHostForwarder(wrappedService, mDozeHost);
         wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
                 wrappedService, mDozeParameters);
         wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
@@ -103,19 +102,19 @@
 
         DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
                 mWakefulnessLifecycle, mBatteryController, mDozeLog, mDockManager,
-                mDozeServiceHost);
+                mDozeHost);
         machine.setParts(new DozeMachine.Part[]{
                 new DozePauser(mHandler, machine, mAlarmManager, mDozeParameters.getPolicy()),
                 new DozeFalsingManagerAdapter(mFalsingManager),
-                createDozeTriggers(dozeService, mAsyncSensorManager, mDozeServiceHost,
+                createDozeTriggers(dozeService, mAsyncSensorManager, mDozeHost,
                         mAlarmManager, config, mDozeParameters, mHandler, wakeLock, machine,
                         mDockManager, mDozeLog),
-                createDozeUi(dozeService, mDozeServiceHost, wakeLock, machine, mHandler,
+                createDozeUi(dozeService, mDozeHost, wakeLock, machine, mHandler,
                         mAlarmManager, mDozeParameters, mDozeLog),
-                new DozeScreenState(wrappedService, mHandler, mDozeServiceHost, mDozeParameters,
+                new DozeScreenState(wrappedService, mHandler, mDozeHost, mDozeParameters,
                         wakeLock),
                 createDozeScreenBrightness(dozeService, wrappedService, mAsyncSensorManager,
-                        mDozeServiceHost, mDozeParameters, mHandler),
+                        mDozeHost, mDozeParameters, mHandler),
                 new DozeWallpaperState(mWallpaperManager, mBiometricUnlockController,
                         mDozeParameters),
                 new DozeDockHandler(config, machine, mDockManager),
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index f93c169..d69c3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -23,6 +23,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -124,9 +125,11 @@
 import com.android.systemui.controls.ui.ControlsUiController;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
@@ -200,6 +203,7 @@
     private final UiEventLogger mUiEventLogger;
     private final NotificationShadeDepthController mDepthController;
     private final BlurUtils mBlurUtils;
+    private final SysUiState mSysUiState;
 
     // Used for RingerModeTracker
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -301,7 +305,7 @@
             @Background Executor backgroundExecutor,
             ControlsListingController controlsListingController,
             ControlsController controlsController, UiEventLogger uiEventLogger,
-            RingerModeTracker ringerModeTracker, @Main Handler handler) {
+            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -330,6 +334,7 @@
         mBlurUtils = blurUtils;
         mRingerModeTracker = ringerModeTracker;
         mControlsController = controlsController;
+        mSysUiState = sysUiState;
         mMainHandler = handler;
 
         // receive broadcasts
@@ -638,7 +643,7 @@
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
                 getWalletPanelViewController(), mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
-                shouldShowControls() ? mControlsUiController : null, mBlurUtils,
+                shouldShowControls() ? mControlsUiController : null, mBlurUtils, mSysUiState,
                 shouldUseControlsLayout(), this::onRotate, mKeyguardShowing);
         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
         dialog.setOnDismissListener(this);
@@ -1920,6 +1925,7 @@
         private final NotificationShadeWindowController mNotificationShadeWindowController;
         private final NotificationShadeDepthController mDepthController;
         private final BlurUtils mBlurUtils;
+        private final SysUiState mSysUiState;
         private final boolean mUseControlsLayout;
         private ListPopupWindow mOverflowPopup;
         private final Runnable mOnRotateCallback;
@@ -1934,7 +1940,8 @@
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
                 ControlsUiController controlsUiController, BlurUtils blurUtils,
-                boolean useControlsLayout, Runnable onRotateCallback, boolean keyguardShowing) {
+                SysUiState sysuiState, boolean useControlsLayout, Runnable onRotateCallback,
+                boolean keyguardShowing) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
             mContext = context;
             mAdapter = adapter;
@@ -1945,6 +1952,7 @@
             mNotificationShadeWindowController = notificationShadeWindowController;
             mControlsUiController = controlsUiController;
             mBlurUtils = blurUtils;
+            mSysUiState = sysuiState;
             mUseControlsLayout = useControlsLayout;
             mOnRotateCallback = onRotateCallback;
             mKeyguardShowing = keyguardShowing;
@@ -2203,6 +2211,8 @@
             mShowing = true;
             mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi();
             mNotificationShadeWindowController.setForceHasTopUi(true);
+            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
+                    .commitUpdate(mContext.getDisplayId());
 
             ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
             root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
@@ -2221,7 +2231,7 @@
             mBackgroundDrawable.setAlpha(0);
             float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
             ObjectAnimator alphaAnimator =
-                    ObjectAnimator.ofFloat(mContainer, "transitionAlpha", 0f, 1f);
+                    ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
             alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
             alphaAnimator.setDuration(183);
             alphaAnimator.addUpdateListener((animation) -> {
@@ -2234,8 +2244,8 @@
 
             ObjectAnimator xAnimator =
                     ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
-            alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-            alphaAnimator.setDuration(350);
+            xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+            xAnimator.setDuration(350);
 
             AnimatorSet animatorSet = new AnimatorSet();
             animatorSet.playTogether(alphaAnimator, xAnimator);
@@ -2247,7 +2257,7 @@
             dismissWithAnimation(() -> {
                 mContainer.setTranslationX(0);
                 ObjectAnimator alphaAnimator =
-                        ObjectAnimator.ofFloat(mContainer, "transitionAlpha", 1f, 0f);
+                        ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f);
                 alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
                 alphaAnimator.setDuration(233);
                 alphaAnimator.addUpdateListener((animation) -> {
@@ -2261,8 +2271,8 @@
                 float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
                 ObjectAnimator xAnimator =
                         ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset);
-                alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-                alphaAnimator.setDuration(350);
+                xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+                xAnimator.setDuration(350);
 
                 AnimatorSet animatorSet = new AnimatorSet();
                 animatorSet.playTogether(alphaAnimator, xAnimator);
@@ -2303,6 +2313,8 @@
             if (mControlsUiController != null) mControlsUiController.hide();
             mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
             mDepthController.updateGlobalDialogVisibility(0, null /* view */);
+            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
+                    .commitUpdate(mContext.getDisplayId());
             super.dismiss();
         }
 
@@ -2362,6 +2374,14 @@
         }
 
         public void refreshDialog() {
+            // ensure dropdown menus are dismissed before re-initializing the dialog
+            dismissPanel();
+            dismissOverflow(true);
+            if (mControlsUiController != null) {
+                mControlsUiController.hide();
+            }
+
+            // re-create dialog
             initializeLayout();
             mGlobalActionsLayout.updateList();
             if (mControlsUiController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
index c7612d4..83046ef 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.HardwareBgDrawable;
@@ -78,6 +79,31 @@
         }
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        boolean anyTruncated = false;
+        ViewGroup listView = getListView();
+        // Check to see if any of the GlobalActionsItems have had their messages truncated
+        for (int i = 0; i < listView.getChildCount(); i++) {
+            View child = listView.getChildAt(i);
+            if (child instanceof GlobalActionsItem) {
+                GlobalActionsItem item = (GlobalActionsItem) child;
+                anyTruncated = anyTruncated || item.isTruncated();
+            }
+        }
+        // If any of the items have been truncated, set the all to single-line marquee
+        if (anyTruncated) {
+            for (int i = 0; i < listView.getChildCount(); i++) {
+                View child = listView.getChildAt(i);
+                if (child instanceof GlobalActionsItem) {
+                    GlobalActionsItem item = (GlobalActionsItem) child;
+                    item.setMarquee(true);
+                }
+            }
+        }
+    }
+
     @VisibleForTesting
     protected float getGridItemSize() {
         return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java
new file mode 100644
index 0000000..07fa592
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import android.content.Context;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * Layout for GlobalActions items.
+ */
+public class GlobalActionsItem extends LinearLayout {
+    public GlobalActionsItem(Context context) {
+        super(context);
+    }
+
+    public GlobalActionsItem(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public GlobalActionsItem(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    private TextView getTextView() {
+        return (TextView) findViewById(R.id.message);
+    }
+
+    /**
+     * Sets this item to marquee or not.
+     */
+    public void setMarquee(boolean marquee) {
+        TextView text = getTextView();
+        text.setSingleLine(marquee);
+        text.setEllipsize(marquee ? TextUtils.TruncateAt.MARQUEE : TextUtils.TruncateAt.END);
+    }
+
+    /**
+     * Determines whether the message for this item has been truncated.
+     */
+    public boolean isTruncated() {
+        TextView message = getTextView();
+        if (message != null) {
+            Layout messageLayout = message.getLayout();
+            if (messageLayout != null) {
+                if (messageLayout.getLineCount() > 0) {
+                    // count the number of ellipses in the last line.
+                    int ellipses = messageLayout.getEllipsisCount(
+                            messageLayout.getLineCount() - 1);
+                    // If ellipses are present, the line was forced to truncate.
+                    return ellipses > 0;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index e08d7a5..1691c53 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -323,10 +323,6 @@
             setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
             setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
             updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
-            if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) {
-                    ((IlluminationDrawable) mViewHolder.getBackground().getBackground())
-                            .setupTouch(mViewHolder.getSeamless(), mViewHolder.getPlayer());
-            }
             mViewHolder.getSeamless().setOnClickListener(v -> {
                 final Intent intent = new Intent()
                         .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
@@ -358,11 +354,6 @@
             button.setContentDescription(mediaAction.getContentDescription());
             PendingIntent actionIntent = mediaAction.getIntent();
 
-            if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) {
-                ((IlluminationDrawable) mViewHolder.getBackground().getBackground())
-                        .setupTouch(button, mViewHolder.getPlayer());
-            }
-
             button.setOnClickListener(v -> {
                 if (actionIntent != null) {
                     try {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 07ce9e4..90c558a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -54,7 +54,7 @@
 private const val LUMINOSITY_THRESHOLD = 0.05f
 private const val SATURATION_MULTIPLIER = 0.8f
 
-private val LOADING = MediaData(false,  0, null, null, null, null, null,
+private val LOADING = MediaData(false, 0, null, null, null, null, null,
         emptyList(), emptyList(), null, null, null)
 
 /**
@@ -191,12 +191,14 @@
         // TODO: b/153736623 look into creating actions when this isn't a media style notification
 
         val packageContext: Context = sbn.getPackageContext(context)
-        for (action in actions) {
-            val mediaAction = MediaAction(
-                    action.getIcon().loadDrawable(packageContext),
-                    action.actionIntent,
-                    action.title)
-            actionIcons.add(mediaAction)
+        if (actions != null) {
+            for (action in actions) {
+                val mediaAction = MediaAction(
+                        action.getIcon().loadDrawable(packageContext),
+                        action.actionIntent,
+                        action.title)
+                actionIcons.add(mediaAction)
+            }
         }
 
         foregroundExcecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index d72c369..8db9dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -299,7 +299,7 @@
             firstPlayer.measure(input)
             // Relayouting is necessary in motionlayout to obtain its size properly ....
             it.layout(0, 0, it.measuredWidth, it.measuredHeight)
-            val result = MeasurementOutput(it.measuredWidth, it.measuredHeight)
+            result = MeasurementOutput(it.measuredWidth, it.measuredHeight)
             it.progress = previousProgress
             if (desiredState != null) {
                 // remeasure it to the old size again!
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 571e18d..764dbe6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -60,6 +60,17 @@
     val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
     val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
 
+    init {
+        (background.background as IlluminationDrawable).let {
+            it.setupTouch(seamless, player)
+            it.setupTouch(action0, player)
+            it.setupTouch(action1, player)
+            it.setupTouch(action2, player)
+            it.setupTouch(action3, player)
+            it.setupTouch(action4, player)
+        }
+    }
+
     fun getAction(id: Int): ImageButton {
         return when (id) {
             R.id.action0 -> action0
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index f900f1e..ccf58ba 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -39,7 +39,7 @@
 public class SysUiState implements Dumpable {
 
     private static final String TAG = SysUiState.class.getSimpleName();
-    public static final boolean DEBUG = true;
+    public static final boolean DEBUG = false;
 
     private @QuickStepContract.SystemUiStateFlags int mFlags;
     private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 394f997..9360517 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -61,12 +61,6 @@
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
     private final Rect mTmpInsets = new Rect();
 
-    /**
-     * Tracks the destination bounds, used for any following
-     * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} calculations.
-     */
-    private final Rect mLastDestinationBounds = new Rect();
-
     private ComponentName mLastPipComponentName;
     private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
     private Size mReentrySize;
@@ -198,11 +192,6 @@
         mReentrySnapFraction = INVALID_SNAP_FRACTION;
         mReentrySize = null;
         mLastPipComponentName = null;
-        mLastDestinationBounds.setEmpty();
-    }
-
-    public Rect getLastDestinationBounds() {
-        return mLastDestinationBounds;
     }
 
     public Rect getDisplayBounds() {
@@ -262,7 +251,6 @@
                     false /* useCurrentMinEdgeSize */);
         }
         mAspectRatio = aspectRatio;
-        mLastDestinationBounds.set(destinationBounds);
         return destinationBounds;
     }
 
@@ -276,8 +264,8 @@
      *
      * @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
      */
-    public boolean onDisplayRotationChanged(Rect outBounds, int displayId, int fromRotation,
-            int toRotation, WindowContainerTransaction t) {
+    public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, int displayId,
+            int fromRotation, int toRotation, WindowContainerTransaction t) {
         // Bail early if the event is not sent to current {@link #mDisplayInfo}
         if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
             return false;
@@ -295,7 +283,7 @@
         }
 
         // Calculate the snap fraction of the current stack along the old movement bounds
-        final Rect postChangeStackBounds = new Rect(mLastDestinationBounds);
+        final Rect postChangeStackBounds = new Rect(oldBounds);
         final float snapFraction = getSnapFraction(postChangeStackBounds);
 
         // Populate the new {@link #mDisplayInfo}.
@@ -313,7 +301,6 @@
                 snapFraction);
 
         outBounds.set(postChangeStackBounds);
-        mLastDestinationBounds.set(outBounds);
         t.setBounds(pinnedStackInfo.stackToken, outBounds);
         return true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index bb627bc..ae61006 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -600,6 +600,7 @@
             Log.w(TAG, "Abort animation, invalid leash");
             return;
         }
+        mLastReportedBounds.set(destinationBounds);
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, destinationBounds)
@@ -709,7 +710,7 @@
      * @return {@code true} if destinationBounds is altered for split screen
      */
     private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) {
-        if (mSplitDivider == null || !mSplitDivider.inSplitMode()) {
+        if (mSplitDivider == null || !mSplitDivider.isDividerVisible()) {
             // bail early if system is not in split screen mode
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 30d6cd9..64df2ff 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -95,7 +95,7 @@
     private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
             int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
         final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds,
-                displayId, fromRotation, toRotation, t);
+                mPipTaskOrganizer.getLastReportedBounds(), displayId, fromRotation, toRotation, t);
         if (changed) {
             updateMovementBounds(mTmpNormalBounds, true /* fromRotation */,
                     false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
@@ -293,7 +293,7 @@
             final boolean changed = mPipBoundsHandler.setShelfHeight(visible, height);
             if (changed) {
                 mTouchHandler.onShelfVisibilityChanged(visible, height);
-                updateMovementBounds(mPipBoundsHandler.getLastDestinationBounds(),
+                updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(),
                         false /* fromRotation */, false /* fromImeAdjustment */,
                         true /* fromShelfAdjustment */);
             }
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 59c962d..d0ff2ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -424,7 +424,7 @@
 
         // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
         // occluded by the IME or shelf.
-        if (fromImeAdjustment || fromShelfAdjustment || fromDisplayRotationChanged) {
+        if (fromImeAdjustment || fromShelfAdjustment) {
             if (mTouchState.isUserInteracting()) {
                 // Defer the update of the current movement bounds until after the user finishes
                 // touching the screen
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index dfd385d..2f06c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -32,7 +32,6 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
@@ -228,6 +227,11 @@
     }
 
     @Override
+    protected boolean shouldShowDetail() {
+        return !mExpanded;
+    }
+
+    @Override
     protected void drawTile(TileRecord r, State state) {
         if (state instanceof SignalState) {
             SignalState copy = new SignalState();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index f821b19..9e2bb98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -36,7 +36,6 @@
 import android.util.Log;
 import android.util.PathParser;
 import android.view.Gravity;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
@@ -44,7 +43,6 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.Switch;
-import android.widget.TextView;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -70,7 +68,6 @@
     private float mStrokeWidthInactive;
 
     private final ImageView mBg;
-    private final TextView mDetailText;
     private final int mColorActive;
     private final int mColorInactive;
     private final int mColorDisabled;
@@ -112,12 +109,6 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
                 Gravity.CENTER);
         mIconFrame.addView(mIcon, params);
-
-        // "..." afforadance below icon
-        mDetailText = (TextView) LayoutInflater.from(context).inflate(R.layout.qs_tile_detail_text,
-                mIconFrame, false);
-        mIconFrame.addView(mDetailText);
-
         mIconFrame.setClipChildren(false);
         mIconFrame.setClipToPadding(false);
 
@@ -173,10 +164,6 @@
             tile.longClick();
             return true;
         });
-
-        if (tile.supportsDetailView()) {
-            mDetailText.setVisibility(View.VISIBLE);
-        }
     }
 
     public void init(OnClickListener click, OnClickListener secondaryClick,
@@ -254,8 +241,6 @@
             mCircleColor = circleColor;
         }
 
-        mDetailText.setTextColor(QSTileImpl.getColorForState(getContext(), state.state));
-
         mShowRippleEffect = state.showRippleEffect;
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
         setLongClickable(state.handlesLongClick);
@@ -400,4 +385,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 7e5f2e19..87faacc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -68,8 +68,6 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QuickStatusBarHeader;
 import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tiles.QSSettingsControllerKt;
-import com.android.systemui.qs.tiles.QSSettingsPanel;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -152,19 +150,12 @@
      */
     abstract public int getMetricsCategory();
 
-    /**
-     * Experimental option on whether to use settings panels. Only loaded on creation, so the tile
-     * needs to be removed and added for this to take effect.
-     */
-    protected final QSSettingsPanel mQSSettingsPanelOption;
-
     protected QSTileImpl(QSHost host) {
         mHost = host;
         mContext = host.getContext();
         mInstanceId = host.getNewInstanceId();
         mState = newTileState();
         mTmpState = newTileState();
-        mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
         mQSLogger = host.getQSLogger();
         mUiEventLogger = host.getUiEventLogger();
     }
@@ -366,10 +357,6 @@
      * {@link QSTileImpl#getLongClickIntent}
      */
     protected void handleLongClick() {
-        if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) {
-            showDetail(true);
-            return;
-        }
         Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
                 getLongClickIntent(), 0);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
deleted file mode 100644
index c7ef0be..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
+++ /dev/null
@@ -1,36 +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 com.android.systemui.qs.tiles
-
-import android.provider.DeviceConfig
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-
-enum class QSSettingsPanel {
-    DEFAULT,
-    OPEN_LONG_PRESS,
-    OPEN_CLICK,
-    USE_DETAIL
-}
-
-fun getQSSettingsPanelOption(): QSSettingsPanel =
-        when (DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.QS_USE_SETTINGS_PANELS, 0)) {
-            1 -> QSSettingsPanel.OPEN_LONG_PRESS
-            2 -> QSSettingsPanel.OPEN_CLICK
-            3 -> QSSettingsPanel.USE_DETAIL
-            else -> QSSettingsPanel.DEFAULT
-        }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index f169501..1279d42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -56,7 +56,6 @@
 /** Quick settings tile: Wifi **/
 public class WifiTile extends QSTileImpl<SignalState> {
     private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
-    private static final Intent WIFI_PANEL = new Intent(Settings.Panel.ACTION_WIFI);
 
     protected final NetworkController mController;
     private final AccessPointController mWifiController;
@@ -109,21 +108,11 @@
 
     @Override
     public Intent getLongClickIntent() {
-        if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_LONG_PRESS) return WIFI_PANEL;
-        else return WIFI_SETTINGS;
-    }
-
-    @Override
-    public boolean supportsDetailView() {
-        return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK;
+        return WIFI_SETTINGS;
     }
 
     @Override
     protected void handleClick() {
-        if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK) {
-            mActivityStarter.postStartActivityDismissingKeyguard(WIFI_PANEL, 0);
-            return;
-        }
         // Secondary clicks are header clicks, just toggle.
         mState.copyTo(mStateBeforeClick);
         boolean wifiEnabled = mState.value;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index cf098d5..960c501 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -156,7 +156,7 @@
                 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
 
                 // Remove notification
-                notificationManager.cancel(NOTIFICATION_RECORDING_ID);
+                notificationManager.cancel(NOTIFICATION_VIEW_ID);
 
                 startActivity(Intent.createChooser(shareIntent, shareLabel)
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -175,7 +175,7 @@
                         Toast.LENGTH_LONG).show();
 
                 // Remove notification
-                notificationManager.cancel(NOTIFICATION_RECORDING_ID);
+                notificationManager.cancel(NOTIFICATION_VIEW_ID);
                 Log.d(TAG, "Deleted recording " + uri);
                 break;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 4148289..6f68ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.screenshot;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
@@ -54,6 +56,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
@@ -61,16 +64,19 @@
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.Toast;
@@ -155,6 +161,9 @@
     static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
     static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
 
+    // From WizardManagerHelper.java
+    private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+
     private static final String TAG = "GlobalScreenshot";
 
     private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
@@ -169,7 +178,7 @@
     private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
     private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
     private static final float ROUNDED_CORNER_RADIUS = .05f;
-    private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000;
+    private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
     private static final int MESSAGE_CORNER_TIMEOUT = 2;
 
     private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
@@ -183,24 +192,25 @@
     private final Display mDisplay;
     private final DisplayMetrics mDisplayMetrics;
 
-    private final View mScreenshotLayout;
-    private final ScreenshotSelectorView mScreenshotSelectorView;
-    private final ImageView mScreenshotAnimatedView;
-    private final ImageView mScreenshotPreview;
-    private final ImageView mScreenshotFlash;
-    private final ImageView mActionsContainerBackground;
-    private final FrameLayout mActionsContainer;
-    private final LinearLayout mActionsView;
-    private final ImageView mBackgroundProtection;
-    private final FrameLayout mDismissButton;
-    private final ImageView mDismissImage;
+    private View mScreenshotLayout;
+    private ScreenshotSelectorView mScreenshotSelectorView;
+    private ImageView mScreenshotAnimatedView;
+    private ImageView mScreenshotPreview;
+    private ImageView mScreenshotFlash;
+    private ImageView mActionsContainerBackground;
+    private HorizontalScrollView mActionsContainer;
+    private LinearLayout mActionsView;
+    private ImageView mBackgroundProtection;
+    private FrameLayout mDismissButton;
 
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
     private Animator mScreenshotAnimation;
     private Runnable mOnCompleteRunnable;
-    private boolean mInDarkMode = false;
     private Animator mDismissAnimation;
+    private boolean mInDarkMode = false;
+    private boolean mDirectionLTR = true;
+    private boolean mOrientationPortrait = true;
 
     private float mScreenshotOffsetXPx;
     private float mScreenshotOffsetYPx;
@@ -232,57 +242,18 @@
      */
     @Inject
     public GlobalScreenshot(
-            Context context, @Main Resources resources, LayoutInflater layoutInflater,
+            Context context, @Main Resources resources,
             ScreenshotNotificationsController screenshotNotificationsController,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mNotificationsController = screenshotNotificationsController;
         mUiEventLogger = uiEventLogger;
 
-        // Inflate the screenshot layout
-        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
-        mScreenshotAnimatedView =
-                mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view);
-        mScreenshotAnimatedView.setClipToOutline(true);
-        mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
-                        ROUNDED_CORNER_RADIUS * view.getWidth());
-            }
-        });
-        mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview);
-        mScreenshotPreview.setClipToOutline(true);
-        mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
-                        ROUNDED_CORNER_RADIUS * view.getWidth());
-            }
-        });
-
-        mActionsContainerBackground = mScreenshotLayout.findViewById(
-                R.id.global_screenshot_actions_container_background);
-        mActionsContainer = mScreenshotLayout.findViewById(
-                R.id.global_screenshot_actions_container);
-        mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
-        mBackgroundProtection = mScreenshotLayout.findViewById(
-                R.id.global_screenshot_actions_background);
-        mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
-        mDismissButton.setOnClickListener(view -> {
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
-            dismissScreenshot("dismiss_button", false);
-            mOnCompleteRunnable.run();
-        });
-        mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image);
-
-        mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
-        mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
-        mScreenshotLayout.setFocusable(true);
-        mScreenshotSelectorView.setFocusable(true);
-        mScreenshotSelectorView.setFocusableInTouchMode(true);
-        mScreenshotAnimatedView.setPivotX(0);
-        mScreenshotAnimatedView.setPivotY(0);
+        reloadAssets();
+        Configuration config = mContext.getResources().getConfiguration();
+        mInDarkMode = config.isNightModeActive();
+        mDirectionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+        mOrientationPortrait = config.orientation == ORIENTATION_PORTRAIT;
 
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -333,6 +304,121 @@
         inoutInfo.touchableRegion.set(touchRegion);
     }
 
+    private void onConfigChanged(Configuration newConfig) {
+        boolean needsUpdate = false;
+        // dark mode
+        if (newConfig.isNightModeActive()) {
+            // Night mode is active, we're using dark theme
+            if (!mInDarkMode) {
+                mInDarkMode = true;
+                needsUpdate = true;
+            }
+        } else {
+            // Night mode is not active, we're using the light theme
+            if (mInDarkMode) {
+                mInDarkMode = false;
+                needsUpdate = true;
+            }
+        }
+
+        // RTL configuration
+        switch (newConfig.getLayoutDirection()) {
+            case View.LAYOUT_DIRECTION_LTR:
+                if (!mDirectionLTR) {
+                    mDirectionLTR = true;
+                    needsUpdate = true;
+                }
+                break;
+            case View.LAYOUT_DIRECTION_RTL:
+                if (mDirectionLTR) {
+                    mDirectionLTR = false;
+                    needsUpdate = true;
+                }
+                break;
+        }
+
+        // portrait/landscape orientation
+        switch (newConfig.orientation) {
+            case ORIENTATION_PORTRAIT:
+                if (!mOrientationPortrait) {
+                    mOrientationPortrait = true;
+                    needsUpdate = true;
+                }
+                break;
+            case ORIENTATION_LANDSCAPE:
+                if (mOrientationPortrait) {
+                    mOrientationPortrait = false;
+                    needsUpdate = true;
+                }
+                break;
+        }
+
+        if (needsUpdate) {
+            reloadAssets();
+        }
+    }
+
+    /**
+     * Update assets (called when the dark theme status changes). We only need to update the dismiss
+     * button and the actions container background, since the buttons are re-inflated on demand.
+     */
+    private void reloadAssets() {
+        boolean wasAttached = mScreenshotLayout != null && mScreenshotLayout.isAttachedToWindow();
+        if (wasAttached) {
+            mWindowManager.removeView(mScreenshotLayout);
+        }
+
+        // Inflate the screenshot layout
+        mScreenshotLayout = LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
+        mScreenshotAnimatedView =
+                mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view);
+        mScreenshotAnimatedView.setClipToOutline(true);
+        mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
+                        ROUNDED_CORNER_RADIUS * view.getWidth());
+            }
+        });
+        mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview);
+        mScreenshotPreview.setClipToOutline(true);
+        mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
+                        ROUNDED_CORNER_RADIUS * view.getWidth());
+            }
+        });
+
+        mActionsContainerBackground = mScreenshotLayout.findViewById(
+                R.id.global_screenshot_actions_container_background);
+        mActionsContainer = mScreenshotLayout.findViewById(
+                R.id.global_screenshot_actions_container);
+        mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
+        mBackgroundProtection = mScreenshotLayout.findViewById(
+                R.id.global_screenshot_actions_background);
+        mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
+        mDismissButton.setOnClickListener(view -> {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+            dismissScreenshot("dismiss_button", false);
+            mOnCompleteRunnable.run();
+        });
+
+        mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
+        mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
+        mScreenshotLayout.setFocusable(true);
+        mScreenshotSelectorView.setFocusable(true);
+        mScreenshotSelectorView.setFocusableInTouchMode(true);
+        mScreenshotAnimatedView.setPivotX(0);
+        mScreenshotAnimatedView.setPivotY(0);
+        mActionsContainer.setScrollX(0);
+
+        if (wasAttached) {
+            mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        }
+    }
+
+
     /**
      * Creates a new worker thread and saves the screenshot to the media store.
      */
@@ -378,14 +464,19 @@
             return;
         }
 
+        if (!isUserSetupComplete()) {
+            // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+            // and sharing shouldn't be exposed to the user.
+            saveScreenshotAndToast(finisher);
+            return;
+        }
+
         // Optimizations
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
 
-        updateDarkTheme();
+        onConfigChanged(mContext.getResources().getConfiguration());
 
-        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
-        mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
 
         if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
             mDismissAnimation.cancel();
@@ -395,7 +486,6 @@
     }
 
     void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
-        dismissScreenshot("new screenshot requested", true);
         mOnCompleteRunnable = onComplete;
 
         mDisplay.getRealMetrics(mDisplayMetrics);
@@ -407,7 +497,6 @@
     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
             Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) {
         // TODO use taskId and visibleInsets
-        dismissScreenshot("new screenshot requested", true);
         mOnCompleteRunnable = onComplete;
         takeScreenshot(screenshot, finisher, screenshotScreenBounds);
     }
@@ -468,6 +557,41 @@
     }
 
     /**
+     * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
+     * failure).
+     */
+    private void saveScreenshotAndToast(Consumer<Uri> finisher) {
+        // Play the shutter sound to notify that we've taken a screenshot
+        mScreenshotHandler.post(() -> {
+            mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+        });
+
+        saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+            @Override
+            void onActionsReady(SavedImageData imageData) {
+                finisher.accept(imageData.uri);
+                if (imageData.uri == null) {
+                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
+                    mNotificationsController.notifyScreenshotError(
+                            R.string.screenshot_failed_to_capture_text);
+                } else {
+                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
+
+                    mScreenshotHandler.post(() -> {
+                        Toast.makeText(mContext, R.string.screenshot_saved_title,
+                                Toast.LENGTH_SHORT).show();
+                    });
+                }
+            }
+        });
+    }
+
+    private boolean isUserSetupComplete() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    }
+
+    /**
      * Clears current screenshot
      */
     private void dismissScreenshot(String reason, boolean immediate) {
@@ -513,53 +637,15 @@
     }
 
     /**
-     * Update assets (called when the dark theme status changes). We only need to update the
-     * dismiss
-     * button and the actions container background, since the buttons are re-inflated on demand.
-     */
-    private void reloadAssets() {
-        mDismissImage.setImageDrawable(mContext.getDrawable(R.drawable.screenshot_cancel));
-        mActionsContainerBackground.setBackground(
-                mContext.getDrawable(R.drawable.action_chip_container_background));
-    }
-
-    /**
-     * Checks the current dark theme status and updates if it has changed.
-     */
-    private void updateDarkTheme() {
-        int currentNightMode = mContext.getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK;
-        switch (currentNightMode) {
-            case Configuration.UI_MODE_NIGHT_NO:
-                // Night mode is not active, we're using the light theme
-                if (mInDarkMode) {
-                    mInDarkMode = false;
-                    reloadAssets();
-                }
-                break;
-            case Configuration.UI_MODE_NIGHT_YES:
-                // Night mode is active, we're using dark theme
-                if (!mInDarkMode) {
-                    mInDarkMode = true;
-                    reloadAssets();
-                }
-                break;
-        }
-    }
-
-    /**
      * Starts the animation after taking the screenshot
      */
     private void startAnimation(final Consumer<Uri> finisher, int w, int h,
             @Nullable Rect screenRect) {
         // If power save is on, show a toast so there is some visual indication that a
-        // screenshot
-        // has been taken.
-        PowerManager powerManager = (PowerManager) mContext.getSystemService(
-                Context.POWER_SERVICE);
+        // screenshot has been taken.
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         if (powerManager.isPowerSaveMode()) {
-            Toast.makeText(mContext, R.string.screenshot_saved_title,
-                    Toast.LENGTH_SHORT).show();
+            Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
         }
 
         mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect);
@@ -588,31 +674,58 @@
                         } else {
                             createScreenshotActionsShadeAnimation(imageData).start();
                         }
+                        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+                        long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
+                                SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
+                                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+
                         mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
                         mScreenshotHandler.sendMessageDelayed(
                                 mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
-                                SCREENSHOT_CORNER_TIMEOUT_MILLIS);
+                                timeoutMs);
                     });
                 }
             }
         });
         mScreenshotHandler.post(() -> {
-            // Play the shutter sound to notify that we've taken a screenshot
-            mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+            if (!mScreenshotLayout.isAttachedToWindow()) {
+                mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+            }
+            mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
 
-            mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            mScreenshotPreview.buildLayer();
-            mScreenshotAnimation.start();
+            mScreenshotHandler.post(() -> {
+
+                // Play the shutter sound to notify that we've taken a screenshot
+                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+                mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                mScreenshotPreview.buildLayer();
+                mScreenshotAnimation.start();
+            });
+
         });
+
     }
 
     private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
-        float cornerScale = mCornerSizeX / (float) width;
+        float screenWidth = mDisplayMetrics.widthPixels;
+        float screenHeight = mDisplayMetrics.heightPixels;
 
-        mScreenshotAnimatedView.setScaleX(1);
-        mScreenshotAnimatedView.setScaleY(1);
-        mScreenshotAnimatedView.setX(0);
-        mScreenshotAnimatedView.setY(0);
+        int rotation = mContext.getDisplay().getRotation();
+        float cornerScale;
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+            cornerScale = (mCornerSizeX /  screenHeight);
+        } else {
+            cornerScale = (mCornerSizeX / screenWidth);
+        }
+        float currentScale = width / screenWidth;
+
+        mScreenshotAnimatedView.setScaleX(currentScale);
+        mScreenshotAnimatedView.setScaleY(currentScale);
+
+        mScreenshotAnimatedView.setPivotX(0);
+        mScreenshotAnimatedView.setPivotY(0);
 
         mScreenshotAnimatedView.setImageBitmap(mScreenBitmap);
         mScreenshotPreview.setImageBitmap(mScreenBitmap);
@@ -632,14 +745,12 @@
 
         final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
         float finalX;
-        if (mContext.getResources().getConfiguration().getLayoutDirection()
-                == View.LAYOUT_DIRECTION_LTR) {
-            finalX = mScreenshotOffsetXPx + width * cornerScale / 2f;
+        if (mDirectionLTR) {
+            finalX = mScreenshotOffsetXPx + screenWidth * cornerScale / 2f;
         } else {
-            finalX = width - mScreenshotOffsetXPx - width * cornerScale / 2f;
+            finalX = screenWidth - mScreenshotOffsetXPx - screenWidth * cornerScale / 2f;
         }
-        float finalY =
-                mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f;
+        float finalY = screenHeight - mScreenshotOffsetYPx - screenHeight * cornerScale / 2f;
         final PointF finalPos = new PointF(finalX, finalY);
 
         ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
@@ -647,13 +758,12 @@
         float xPositionPct =
                 SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
         float scalePct =
-                SCREENSHOT_TO_CORNER_SCALE_DURATION_MS
-                        / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+                SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
         toCorner.addUpdateListener(animation -> {
             float t = animation.getAnimatedFraction();
             if (t < scalePct) {
                 float scale = MathUtils.lerp(
-                        1, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct));
+                        currentScale, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct));
                 mScreenshotAnimatedView.setScaleX(scale);
                 mScreenshotAnimatedView.setScaleY(scale);
             } else {
@@ -667,13 +777,13 @@
             if (t < xPositionPct) {
                 float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
                         mFastOutSlowIn.getInterpolation(t / xPositionPct));
-                mScreenshotAnimatedView.setX(xCenter - width * currentScaleX / 2f);
+                mScreenshotAnimatedView.setX(xCenter - screenWidth * currentScaleX / 2f);
             } else {
-                mScreenshotAnimatedView.setX(finalPos.x - width * currentScaleX / 2f);
+                mScreenshotAnimatedView.setX(finalPos.x - screenWidth * currentScaleX / 2f);
             }
             float yCenter = MathUtils.lerp(startPos.y, finalPos.y,
                     mFastOutSlowIn.getInterpolation(t));
-            mScreenshotAnimatedView.setY(yCenter - height * currentScaleY / 2f);
+            mScreenshotAnimatedView.setY(yCenter - screenHeight * currentScaleY / 2f);
         });
 
         toCorner.addListener(new AnimatorListenerAdapter() {
@@ -713,7 +823,6 @@
     private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mActionsView.removeAllViews();
-        mActionsContainer.setScrollX(0);
         mScreenshotLayout.invalidate();
         mScreenshotLayout.requestLayout();
         mScreenshotLayout.getViewTreeObserver().dispatchOnGlobalLayout();
@@ -803,14 +912,11 @@
         animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
         float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
                 / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
-        mActionsContainer.setVisibility(View.VISIBLE);
         mActionsContainer.setAlpha(0f);
         mActionsContainerBackground.setAlpha(0f);
+        mActionsContainer.setVisibility(View.VISIBLE);
         mActionsContainerBackground.setVisibility(View.VISIBLE);
 
-        mActionsContainer.setPivotX(0);
-        mActionsContainerBackground.setPivotX(0);
-
         animator.addUpdateListener(animation -> {
             float t = animation.getAnimatedFraction();
             mBackgroundProtection.setAlpha(t);
@@ -825,6 +931,10 @@
                 chip.setAlpha(t);
                 chip.setScaleX(1 / containerScale); // invert to keep size of children constant
             }
+            mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
+            mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
+            mActionsContainerBackground.setPivotX(
+                    mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
         });
         return animator;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 4f20492..f7f1223 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -92,7 +92,7 @@
     }
 
     private void handleDockKey(long shortcutCode) {
-        if (mDivider == null || !mDivider.inSplitMode()) {
+        if (mDivider == null || !mDivider.isDividerVisible()) {
             // Split the screen
             mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
                     ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index db33c79..cdd1280 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.stackdivider;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
@@ -123,14 +124,17 @@
                 SplitDisplayLayout sdl = new SplitDisplayLayout(mContext, displayLayout, mSplits);
                 sdl.rotateTo(toRotation);
                 mRotateSplitLayout = sdl;
-                int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
-                        : mView.getCurrentPosition();
+                final int position = isDividerVisible()
+                        ? (mMinimized ? mView.mSnapTargetBeforeMinimized.position
+                                : mView.getCurrentPosition())
+                        // snap resets to middle target when not in split-mode
+                        : sdl.getSnapAlgorithm().getMiddleTarget().position;
                 DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
                 final DividerSnapAlgorithm.SnapTarget target =
                         snap.calculateNonDismissingSnapTarget(position);
                 sdl.resizeSplits(target.position, t);
 
-                if (inSplitMode()) {
+                if (isSplitActive()) {
                     WindowManagerProxy.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
                 }
             };
@@ -199,7 +203,7 @@
         @Override
         public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
                 boolean imeShouldShow, SurfaceControl.Transaction t) {
-            if (!inSplitMode()) {
+            if (!isDividerVisible()) {
                 return;
             }
             final boolean splitIsVisible = !mView.isHidden();
@@ -298,7 +302,7 @@
         @Override
         public void onImePositionChanged(int displayId, int imeTop,
                 SurfaceControl.Transaction t) {
-            if (mAnimation != null || !inSplitMode() || mPaused) {
+            if (mAnimation != null || !isDividerVisible() || mPaused) {
                 // Not synchronized with IME anymore, so return.
                 return;
             }
@@ -310,7 +314,7 @@
         @Override
         public void onImeEndPositioning(int displayId, boolean cancelled,
                 SurfaceControl.Transaction t) {
-            if (mAnimation != null || !inSplitMode() || mPaused) {
+            if (mAnimation != null || !isDividerVisible() || mPaused) {
                 // Not synchronized with IME anymore, so return.
                 return;
             }
@@ -479,7 +483,7 @@
 
             @Override
             public void onKeyguardShowingChanged() {
-                if (!inSplitMode() || mView == null) {
+                if (!isDividerVisible() || mView == null) {
                     return;
                 }
                 mView.setHidden(mKeyguardStateController.isShowing());
@@ -559,10 +563,20 @@
     }
 
     /** {@code true} if this is visible */
-    public boolean inSplitMode() {
+    public boolean isDividerVisible() {
         return mView != null && mView.getVisibility() == View.VISIBLE;
     }
 
+    /**
+     * This indicates that at-least one of the splits has content. This differs from
+     * isDividerVisible because the divider is only visible once *everything* is in split mode
+     * while this only cares if some things are (eg. while entering/exiting as well).
+     */
+    private boolean isSplitActive() {
+        return mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
+                || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED;
+    }
+
     private void addDivider(Configuration configuration) {
         Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
         mView = (DividerView)
@@ -635,8 +649,8 @@
     }
 
     void onSplitDismissed() {
-        mMinimized = false;
         updateVisibility(false /* visible */);
+        mMinimized = false;
         removeDivider();
     }
 
@@ -655,7 +669,8 @@
     private void setHomeMinimized(final boolean minimized, boolean homeStackResizable) {
         if (DEBUG) {
             Slog.d(TAG, "setHomeMinimized  min:" + mMinimized + "->" + minimized + " hrsz:"
-                    + mHomeStackResizable + "->" + homeStackResizable + " split:" + inSplitMode());
+                    + mHomeStackResizable + "->" + homeStackResizable
+                    + " split:" + isDividerVisible());
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         final boolean minimizedChanged = mMinimized != minimized;
@@ -670,7 +685,7 @@
         final boolean homeResizableChanged = mHomeStackResizable != homeStackResizable;
         if (homeResizableChanged) {
             mHomeStackResizable = homeStackResizable;
-            if (inSplitMode()) {
+            if (isDividerVisible()) {
                 WindowManagerProxy.applyHomeTasksMinimized(
                         mSplitLayout, mSplits.mSecondary.token, wct);
             }
@@ -780,7 +795,7 @@
 
     /** Register a listener that gets called whenever the existence of the divider changes */
     public void registerInSplitScreenListener(Consumer<Boolean> listener) {
-        listener.accept(inSplitMode());
+        listener.accept(isDividerVisible());
         synchronized (mDockedStackExistsListeners) {
             mDockedStackExistsListeners.add(new WeakReference<>(listener));
         }
@@ -795,7 +810,7 @@
 
     void ensureMinimizedSplit() {
         setHomeMinimized(true /* minimized */, mSplits.mSecondary.isResizable());
-        if (!inSplitMode()) {
+        if (!isDividerVisible()) {
             // Wasn't in split-mode yet, so enter now.
             if (DEBUG) {
                 Slog.d(TAG, " entering split mode with minimized=true");
@@ -806,7 +821,7 @@
 
     void ensureNormalSplit() {
         setHomeMinimized(false /* minimized */, mHomeStackResizable);
-        if (!inSplitMode()) {
+        if (!isDividerVisible()) {
             // Wasn't in split-mode, so enter now.
             if (DEBUG) {
                 Slog.d(TAG, " enter split mode unminimized ");
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 060760a..db89cea 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -28,12 +28,14 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.Region.Op;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.util.Slog;
 import android.view.Display;
@@ -164,6 +166,9 @@
     int mDividerPositionX;
     int mDividerPositionY;
 
+    private final Matrix mTmpMatrix = new Matrix();
+    private final float[] mTmpValues = new float[9];
+
     // The view is removed or in the process of been removed from the system.
     private boolean mRemoved;
 
@@ -249,6 +254,22 @@
         }
     };
 
+    private Runnable mUpdateEmbeddedMatrix = () -> {
+        if (getViewRootImpl() == null) {
+            return;
+        }
+        if (isHorizontalDivision()) {
+            mTmpMatrix.setTranslate(0, mDividerPositionY - mDividerInsets);
+        } else {
+            mTmpMatrix.setTranslate(mDividerPositionX - mDividerInsets, 0);
+        }
+        mTmpMatrix.getValues(mTmpValues);
+        try {
+            getViewRootImpl().getAccessibilityEmbeddedConnection().setScreenMatrix(mTmpValues);
+        } catch (RemoteException e) {
+        }
+    };
+
     public DividerView(Context context) {
         this(context, null);
     }
@@ -1084,6 +1105,10 @@
                 t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0);
             }
         }
+        if (getViewRootImpl() != null) {
+            mHandler.removeCallbacks(mUpdateEmbeddedMatrix);
+            mHandler.post(mUpdateEmbeddedMatrix);
+        }
     }
 
     void setResizeDimLayer(Transaction t, boolean primary, float alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index 2862c83..c496d22 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -193,7 +193,7 @@
                 Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
                         + "  " + mSecondary.topActivityType);
             }
-            if (mDivider.inSplitMode()) {
+            if (mDivider.isDividerVisible()) {
                 // Was in split-mode, which means we are leaving split, so continue that.
                 // This happens when the stack in the primary-split is dismissed.
                 if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
new file mode 100644
index 0000000..7f7ff9cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -0,0 +1,88 @@
+/*
+ * 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
+
+import android.app.PendingIntent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * Logger class for events related to the user clicking on notification actions
+ */
+class ActionClickLogger @Inject constructor(
+    @NotifInteractionLog private val buffer: LogBuffer
+) {
+    fun logInitialClick(
+        entry: NotificationEntry?,
+        pendingIntent: PendingIntent
+    ) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry?.key
+            str2 = entry?.ranking?.channel?.id
+            str3 = pendingIntent.intent.toString()
+        }, {
+            "ACTION CLICK $str1 (channel=$str2) for pending intent $str3"
+        })
+    }
+
+    fun logRemoteInputWasHandled(
+        entry: NotificationEntry?
+    ) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry?.key
+        }, {
+            "  [Action click] Triggered remote input (for $str1))"
+        })
+    }
+
+    fun logStartingIntentWithDefaultHandler(
+        entry: NotificationEntry?,
+        pendingIntent: PendingIntent
+    ) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry?.key
+            str2 = pendingIntent.intent.toString()
+        }, {
+            "  [Action click] Launching intent $str2 via default handler (for $str1)"
+        })
+    }
+
+    fun logWaitingToCloseKeyguard(
+        pendingIntent: PendingIntent
+    ) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = pendingIntent.intent.toString()
+        }, {
+            "  [Action click] Intent $str1 launches an activity, dismissing keyguard first..."
+        })
+    }
+
+    fun logKeyguardGone(
+        pendingIntent: PendingIntent
+    ) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = pendingIntent.intent.toString()
+        }, {
+            "  [Action click] Keyguard dismissed, calling default handler for intent $str1"
+        })
+    }
+}
+
+private const val TAG = "ActionClickLogger"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2647c04..2baab61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,8 +19,8 @@
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
 
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index bf28040..9181c69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,7 +54,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.dagger.StatusBarModule;
+import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -114,6 +114,7 @@
     private final SmartReplyController mSmartReplyController;
     private final NotificationEntryManager mEntryManager;
     private final Handler mMainHandler;
+    private final ActionClickLogger mLogger;
 
     private final Lazy<StatusBar> mStatusBarLazy;
 
@@ -138,14 +139,18 @@
             mStatusBarLazy.get().wakeUpIfDozing(SystemClock.uptimeMillis(), view,
                     "NOTIFICATION_CLICK");
 
+            final NotificationEntry entry = getNotificationForParent(view.getParent());
+            mLogger.logInitialClick(entry, pendingIntent);
+
             if (handleRemoteInput(view, pendingIntent)) {
+                mLogger.logRemoteInputWasHandled(entry);
                 return true;
             }
 
             if (DEBUG) {
                 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
             }
-            logActionClick(view, pendingIntent);
+            logActionClick(view, entry, pendingIntent);
             // The intent we are sending is for the application, which
             // won't have permission to immediately start an activity after
             // the user switches to home.  We know it is safe to do at this
@@ -158,11 +163,15 @@
                 Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
                 options.second.setLaunchWindowingMode(
                         WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+                mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent);
                 return RemoteViews.startPendingIntent(view, pendingIntent, options);
             });
         }
 
-        private void logActionClick(View view, PendingIntent actionIntent) {
+        private void logActionClick(
+                View view,
+                NotificationEntry entry,
+                PendingIntent actionIntent) {
             Integer actionIndex = (Integer)
                     view.getTag(com.android.internal.R.id.notification_action_index_tag);
             if (actionIndex == null) {
@@ -170,7 +179,7 @@
                 return;
             }
             ViewParent parent = view.getParent();
-            StatusBarNotification statusBarNotification = getNotificationForParent(parent);
+            StatusBarNotification statusBarNotification = entry.getSbn();
             if (statusBarNotification == null) {
                 Log.w(TAG, "Couldn't determine notification for click.");
                 return;
@@ -212,10 +221,10 @@
             }
         }
 
-        private StatusBarNotification getNotificationForParent(ViewParent parent) {
+        private NotificationEntry getNotificationForParent(ViewParent parent) {
             while (parent != null) {
                 if (parent instanceof ExpandableNotificationRow) {
-                    return ((ExpandableNotificationRow) parent).getEntry().getSbn();
+                    return ((ExpandableNotificationRow) parent).getEntry();
                 }
                 parent = parent.getParent();
             }
@@ -255,7 +264,7 @@
     };
 
     /**
-     * Injected constructor. See {@link StatusBarModule}.
+     * Injected constructor. See {@link StatusBarDependenciesModule}.
      */
     public NotificationRemoteInputManager(
             Context context,
@@ -265,13 +274,15 @@
             Lazy<StatusBar> statusBarLazy,
             StatusBarStateController statusBarStateController,
             @Main Handler mainHandler,
-            RemoteInputUriController remoteInputUriController) {
+            RemoteInputUriController remoteInputUriController,
+            ActionClickLogger logger) {
         mContext = context;
         mLockscreenUserManager = lockscreenUserManager;
         mSmartReplyController = smartReplyController;
         mEntryManager = notificationEntryManager;
         mStatusBarLazy = statusBarLazy;
         mMainHandler = mainHandler;
+        mLogger = logger;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 765f85a..3dda15b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -186,8 +186,7 @@
 
             boolean groupChangesAllowed =
                     mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
-                    || !ent.hasFinishedInitialization() // notif recently added
-                    || !mListContainer.containsView(ent.getRow()); // notif recently unfiltered
+                    || !ent.hasFinishedInitialization(); // notif recently added
 
             NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn());
             if (!groupChangesAllowed) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index f0fed13..b08eb9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.MediaArtworkProcessor;
 import com.android.systemui.statusbar.NotificationListener;
@@ -73,7 +74,8 @@
             Lazy<StatusBar> statusBarLazy,
             StatusBarStateController statusBarStateController,
             Handler mainHandler,
-            RemoteInputUriController remoteInputUriController) {
+            RemoteInputUriController remoteInputUriController,
+            ActionClickLogger actionClickLogger) {
         return new NotificationRemoteInputManager(
                 context,
                 lockscreenUserManager,
@@ -82,7 +84,8 @@
                 statusBarLazy,
                 statusBarStateController,
                 mainHandler,
-                remoteInputUriController);
+                remoteInputUriController,
+                actionClickLogger);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index d7b391f..ce6013f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -21,12 +21,12 @@
 
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
+import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
+import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.util.DeviceConfigProxy
 import com.android.systemui.util.Utils
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index b1b6a1c..3517e24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -92,6 +92,9 @@
         AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property);
         if (currentValue.equals(newEndValue)) {
             // Skip the animation!
+            if (previousAnimator != null) {
+                previousAnimator.cancel();
+            }
             if (listener != null) {
                 listener.onAnimationEnd(null);
             }
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 634872d..1eadd9e 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
@@ -31,7 +31,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 
 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
 
 import static java.util.Objects.requireNonNull;
 
@@ -68,7 +68,7 @@
 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.stack.NotificationSectionsManager;
+import com.android.systemui.statusbar.notification.stack.PriorityBucket;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -151,6 +151,8 @@
     public CharSequence headsUpStatusBarText;
     public CharSequence headsUpStatusBarTextPublic;
 
+    // indicates when this entry's view was first attached to a window
+    // this value will reset when the view is completely removed from the shade (ie: filtered out)
     private long initializationTime = -1;
 
     /**
@@ -409,12 +411,12 @@
         return wasBubble != isBubble();
     }
 
-    @NotificationSectionsManager.PriorityBucket
+    @PriorityBucket
     public int getBucket() {
         return mBucket;
     }
 
-    public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
+    public void setBucket(@PriorityBucket int bucket) {
         mBucket = bucket;
     }
 
@@ -473,8 +475,8 @@
     }
 
     public boolean hasFinishedInitialization() {
-        return initializationTime == -1
-                || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+        return initializationTime != -1
+                && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
     }
 
     public int getContrastedColor(Context context, boolean isLowPriority,
@@ -565,6 +567,10 @@
         return false;
     }
 
+    public void resetInitializationTime() {
+        initializationTime = -1;
+    }
+
     public void setInitializationTime(long time) {
         if (initializationTime == -1) {
             initializationTime = time;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 9ac4229..dc0b802 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -28,12 +28,11 @@
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.PriorityBucket
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
 import com.android.systemui.statusbar.phone.NotificationGroupManager
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import dagger.Lazy
@@ -135,26 +134,20 @@
     ): List<NotificationEntry> {
         logger.logFilterAndSort(reason)
         val filtered = entries.asSequence()
-                .filterNot(notifFilter::shouldFilterOut)
+                .filterNot(this::filter)
                 .sortedWith(rankingComparator)
                 .toList()
-        assignBuckets(filtered)
+        entries.forEach { it.bucket = getBucketForEntry(it) }
         return filtered
     }
 
-    private fun assignBuckets(entries: List<NotificationEntry>) {
-        entries.forEach { it.bucket = getBucketForEntry(it) }
-        if (!usePeopleFiltering) {
-            // If we don't have a Conversation section, just assign buckets normally based on the
-            // content.
-            return
+    private fun filter(entry: NotificationEntry): Boolean {
+        val filtered = notifFilter.shouldFilterOut(entry)
+        if (filtered) {
+            // notification is removed from the list, so we reset its initialization time
+            entry.resetInitializationTime()
         }
-        // If HUNs are not continuous with the top section, break out into a new Incoming section.
-        entries.asReversed().asSequence().zipWithNext().forEach { (next, entry) ->
-            if (entry.isRowHeadsUp && entry.bucket > next.bucket) {
-                entry.bucket = BUCKET_HEADS_UP
-            }
-        }
+        return filtered
     }
 
     @PriorityBucket
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 0a3b02c..4cec383 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
@@ -713,6 +713,10 @@
     private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
         final NotifFilter filter = findRejectingFilter(entry, now, filters);
         entry.getAttachState().setExcludingFilter(filter);
+        if (filter != null) {
+            // notification is removed from the list, so we reset its initialization time
+            entry.resetInitializationTime();
+        }
         return filter != null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 57be1a5..92426e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -19,8 +19,6 @@
 import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
-import android.annotation.NonNull;
-
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -123,7 +121,7 @@
     private final BubbleController.NotifCallback mNotifCallback =
             new BubbleController.NotifCallback() {
         @Override
-        public void removeNotification(@NonNull final NotificationEntry entry, int reason) {
+        public void removeNotification(NotificationEntry entry, int reason) {
             if (isInterceptingDismissal(entry)) {
                 mInterceptedDismissalEntries.remove(entry.getKey());
                 mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
@@ -143,7 +141,7 @@
         }
 
         @Override
-        public void maybeCancelSummary(@NonNull final NotificationEntry entry) {
+        public void maybeCancelSummary(NotificationEntry entry) {
             // no-op
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 9d456ef..bad36bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -32,8 +32,8 @@
  * Represents the bounds of a section of the notification shade and handles animation when the
  * bounds change.
  */
-class NotificationSection {
-    private @NotificationSectionsManager.PriorityBucket int mBucket;
+public class NotificationSection {
+    private @PriorityBucket int mBucket;
     private View mOwningView;
     private Rect mBounds = new Rect();
     private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
@@ -44,7 +44,7 @@
     private ActivatableNotificationView mFirstVisibleChild;
     private ActivatableNotificationView mLastVisibleChild;
 
-    NotificationSection(View owningView, @NotificationSectionsManager.PriorityBucket int bucket) {
+    NotificationSection(View owningView, @PriorityBucket int bucket) {
         mOwningView = owningView;
         mBucket = bucket;
     }
@@ -74,7 +74,7 @@
         return mBottomAnimator != null || mTopAnimator != null;
     }
 
-    @NotificationSectionsManager.PriorityBucket
+    @PriorityBucket
     public int getBucket() {
         return mBucket;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index 92fdd64..17b4143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -52,14 +52,35 @@
             { "$int1: other ($str1)" }
     )
 
-    fun logHeadsUp(position: Int) = logPosition(position, "Heads Up")
-    fun logConversation(position: Int) = logPosition(position, "Conversation")
-    fun logAlerting(position: Int) = logPosition(position, "Alerting")
-    fun logSilent(position: Int) = logPosition(position, "Silent")
-    fun logForegroundService(position: Int) = logPosition(position, "Foreground Service")
+    fun logHeadsUp(position: Int, isHeadsUp: Boolean) =
+            logPosition(position, "Heads Up", isHeadsUp)
+    fun logConversation(position: Int, isHeadsUp: Boolean) =
+            logPosition(position, "Conversation", isHeadsUp)
+    fun logAlerting(position: Int, isHeadsUp: Boolean) =
+            logPosition(position, "Alerting", isHeadsUp)
+    fun logSilent(position: Int, isHeadsUp: Boolean) =
+            logPosition(position, "Silent", isHeadsUp)
+    fun logForegroundService(position: Int, isHeadsUp: Boolean) =
+            logPosition(position, "Foreground Service", isHeadsUp)
 
     fun logStr(str: String) = logBuffer.log(TAG, LogLevel.DEBUG, { str1 = str }, { "$str1" })
 
+    private fun logPosition(position: Int, label: String, isHeadsUp: Boolean) {
+        val headsUpTag = if (isHeadsUp) " (HUN)" else ""
+        logBuffer.log(
+                TAG,
+                LogLevel.DEBUG,
+                {
+                    int1 = position
+                    str1 = label
+                    str2 = headsUpTag
+                },
+                {
+                    "$int1: $str1$str2"
+                }
+        )
+    }
+
     private fun logPosition(position: Int, label: String) = logBuffer.log(
             TAG,
             LogLevel.DEBUG,
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
deleted file mode 100644
index f3ee2ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ /dev/null
@@ -1,689 +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 com.android.systemui.statusbar.notification.stack;
-
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.ColorInt;
-import android.annotation.IntDef;
-import android.annotation.LayoutRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.people.DataListener;
-import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
-import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary;
-import com.android.systemui.statusbar.notification.people.PersonViewModel;
-import com.android.systemui.statusbar.notification.people.Subscription;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-import kotlin.sequences.Sequence;
-
-/**
- * Manages the boundaries of the two notification sections (high priority and low priority). Also
- * shows/hides the headers for those sections where appropriate.
- *
- * TODO: Move remaining sections logic from NSSL into this class.
- */
-public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
-
-    private static final String TAG = "NotifSectionsManager";
-    private static final boolean DEBUG = false;
-    private static final boolean ENABLE_SNOOZED_CONVERSATION_HUB = false;
-
-    private final ActivityStarter mActivityStarter;
-    private final StatusBarStateController mStatusBarStateController;
-    private final ConfigurationController mConfigurationController;
-    private final PeopleHubViewAdapter mPeopleHubViewAdapter;
-    private final NotificationSectionsFeatureManager mSectionsFeatureManager;
-    private final KeyguardMediaController mKeyguardMediaController;
-    private final int mNumberOfSections;
-    private final NotificationSectionsLogger mLogger;
-    private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
-        @Override
-        public void setVisible(boolean isVisible) {
-            if (mPeopleHubVisible != isVisible) {
-                mPeopleHubVisible = isVisible;
-                if (mInitialized) {
-                    updateSectionBoundaries("PeopleHub visibility changed");
-                }
-            }
-        }
-
-        @NonNull
-        @Override
-        public View getAssociatedViewForClickAnimation() {
-            return mPeopleHubView;
-        }
-
-        @NonNull
-        @Override
-        public Sequence<DataListener<PersonViewModel>> getPersonViewAdapters() {
-            return mPeopleHubView.getPersonViewAdapters();
-        }
-    };
-
-    private NotificationStackScrollLayout mParent;
-    private boolean mInitialized = false;
-
-    private SectionHeaderView mGentleHeader;
-    @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
-
-    private SectionHeaderView mAlertingHeader;
-    private SectionHeaderView mIncomingHeader;
-
-    private PeopleHubView mPeopleHubView;
-    private boolean mPeopleHubVisible = false;
-    @Nullable private Subscription mPeopleHubSubscription;
-
-    private MediaHeaderView mMediaControlsView;
-
-    @Inject
-    NotificationSectionsManager(
-            ActivityStarter activityStarter,
-            StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
-            PeopleHubViewAdapter peopleHubViewAdapter,
-            KeyguardMediaController keyguardMediaController,
-            NotificationSectionsFeatureManager sectionsFeatureManager,
-            NotificationSectionsLogger logger) {
-
-        mActivityStarter = activityStarter;
-        mStatusBarStateController = statusBarStateController;
-        mConfigurationController = configurationController;
-        mPeopleHubViewAdapter = peopleHubViewAdapter;
-        mSectionsFeatureManager = sectionsFeatureManager;
-        mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
-        mKeyguardMediaController = keyguardMediaController;
-        mLogger = logger;
-    }
-
-    NotificationSection[] createSectionsForBuckets() {
-        int[] buckets = mSectionsFeatureManager.getNotificationBuckets();
-        NotificationSection[] sections = new NotificationSection[buckets.length];
-        for (int i = 0; i < buckets.length; i++) {
-            sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
-        }
-
-        return sections;
-    }
-
-    /** Must be called before use. */
-    void initialize(
-            NotificationStackScrollLayout parent, LayoutInflater layoutInflater) {
-        if (mInitialized) {
-            throw new IllegalStateException("NotificationSectionsManager already initialized");
-        }
-        mInitialized = true;
-        mParent = parent;
-        reinflateViews(layoutInflater);
-        mConfigurationController.addCallback(mConfigurationListener);
-    }
-
-    private <T extends ExpandableView> T reinflateView(
-            T view, LayoutInflater layoutInflater, @LayoutRes int layoutResId) {
-        int oldPos = -1;
-        if (view != null) {
-            if (view.getTransientContainer() != null) {
-                view.getTransientContainer().removeView(mGentleHeader);
-            } else if (view.getParent() != null) {
-                oldPos = mParent.indexOfChild(view);
-                mParent.removeView(view);
-            }
-        }
-
-        view = (T) layoutInflater.inflate(layoutResId, mParent, false);
-
-        if (oldPos != -1) {
-            mParent.addView(view, oldPos);
-        }
-
-        return view;
-    }
-
-    /**
-     * Reinflates the entire notification header, including all decoration views.
-     */
-    void reinflateViews(LayoutInflater layoutInflater) {
-        mGentleHeader = reinflateView(
-                mGentleHeader, layoutInflater, R.layout.status_bar_notification_section_header);
-        mGentleHeader.setHeaderText(R.string.notification_section_header_gentle);
-        mGentleHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
-        mGentleHeader.setOnClearAllClickListener(this::onClearGentleNotifsClick);
-
-        mAlertingHeader = reinflateView(
-                mAlertingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
-        mAlertingHeader.setHeaderText(R.string.notification_section_header_alerting);
-        mAlertingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
-
-        if (mPeopleHubSubscription != null) {
-            mPeopleHubSubscription.unsubscribe();
-        }
-        mPeopleHubView = reinflateView(mPeopleHubView, layoutInflater, R.layout.people_strip);
-        if (ENABLE_SNOOZED_CONVERSATION_HUB) {
-            mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
-        }
-
-        mIncomingHeader = reinflateView(
-                mIncomingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
-        mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
-        mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
-
-        mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
-                R.layout.keyguard_media_header);
-        mKeyguardMediaController.attach(mMediaControlsView);
-    }
-
-    /** Listener for when the "clear all" button is clicked on the gentle notification header. */
-    void setOnClearGentleNotifsClickListener(View.OnClickListener listener) {
-        mOnClearGentleNotifsClickListener = listener;
-    }
-
-    @Override
-    public boolean beginsSection(@NonNull View view, @Nullable View previous) {
-        return view == mGentleHeader
-                || view == mMediaControlsView
-                || view == mPeopleHubView
-                || view == mAlertingHeader
-                || view == mIncomingHeader
-                || !Objects.equals(getBucket(view), getBucket(previous));
-    }
-
-    private boolean isUsingMultipleSections() {
-        return mNumberOfSections > 1;
-    }
-
-    @Nullable
-    private Integer getBucket(View view) {
-        if (view == mGentleHeader) {
-            return BUCKET_SILENT;
-        } else if (view == mIncomingHeader) {
-            return BUCKET_HEADS_UP;
-        } else if (view == mMediaControlsView) {
-            return BUCKET_MEDIA_CONTROLS;
-        } else if (view == mPeopleHubView) {
-            return BUCKET_PEOPLE;
-        } else if (view == mAlertingHeader) {
-            return BUCKET_ALERTING;
-        } else if (view instanceof ExpandableNotificationRow) {
-            return ((ExpandableNotificationRow) view).getEntry().getBucket();
-        }
-        return null;
-    }
-
-    private void logShadeContents() {
-        final int childCount = mParent.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mParent.getChildAt(i);
-            if (child == mIncomingHeader) {
-                mLogger.logIncomingHeader(i);
-                continue;
-            }
-            if (child == mMediaControlsView) {
-                mLogger.logMediaControls(i);
-                continue;
-            }
-            if (child == mPeopleHubView) {
-                mLogger.logConversationsHeader(i);
-                continue;
-            }
-            if (child == mAlertingHeader) {
-                mLogger.logAlertingHeader(i);
-                continue;
-            }
-            if (child == mGentleHeader) {
-                mLogger.logSilentHeader(i);
-                continue;
-            }
-
-            if (!(child instanceof ExpandableNotificationRow)) {
-                mLogger.logOther(i, child.getClass());
-                continue;
-            }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            // Once we enter a new section, calculate the target position for the header.
-            switch (row.getEntry().getBucket()) {
-                case BUCKET_HEADS_UP:
-                    mLogger.logHeadsUp(i);
-                    break;
-                case BUCKET_PEOPLE:
-                    mLogger.logConversation(i);
-                    break;
-                case BUCKET_ALERTING:
-                    mLogger.logAlerting(i);
-                    break;
-                case BUCKET_SILENT:
-                    mLogger.logSilent(i);
-                    break;
-            }
-        }
-    }
-
-    @VisibleForTesting
-    void updateSectionBoundaries() {
-        updateSectionBoundaries("test");
-    }
-
-    /**
-     * Should be called whenever notifs are added, removed, or updated. Updates section boundary
-     * bookkeeping and adds/moves/removes section headers if appropriate.
-     */
-    void updateSectionBoundaries(String reason) {
-        if (!isUsingMultipleSections()) {
-            return;
-        }
-
-        mLogger.logStartSectionUpdate(reason);
-
-        // The overall strategy here is to iterate over the current children of mParent, looking
-        // for where the sections headers are currently positioned, and where each section begins.
-        // Then, once we find the start of a new section, we track that position as the "target" for
-        // the section header, adjusted for the case where existing headers are in front of that
-        // target, but won't be once they are moved / removed after the pass has completed.
-
-        final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
-        final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
-        final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
-
-        boolean peopleNotifsPresent = false;
-
-        int currentMediaControlsIdx = -1;
-        int mediaControlsTarget = usingMediaControls ? 0 : -1;
-        int currentIncomingHeaderIdx = -1;
-        int incomingHeaderTarget = -1;
-        int currentPeopleHeaderIdx = -1;
-        int peopleHeaderTarget = -1;
-        int currentAlertingHeaderIdx = -1;
-        int alertingHeaderTarget = -1;
-        int currentGentleHeaderIdx = -1;
-        int gentleHeaderTarget = -1;
-
-        int lastNotifIndex = 0;
-
-        final int childCount = mParent.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mParent.getChildAt(i);
-
-            // Track the existing positions of the headers
-            if (child == mIncomingHeader) {
-                mLogger.logIncomingHeader(i);
-                currentIncomingHeaderIdx = i;
-                continue;
-            }
-            if (child == mMediaControlsView) {
-                mLogger.logMediaControls(i);
-                currentMediaControlsIdx = i;
-                continue;
-            }
-            if (child == mPeopleHubView) {
-                mLogger.logConversationsHeader(i);
-                currentPeopleHeaderIdx = i;
-                continue;
-            }
-            if (child == mAlertingHeader) {
-                mLogger.logAlertingHeader(i);
-                currentAlertingHeaderIdx = i;
-                continue;
-            }
-            if (child == mGentleHeader) {
-                mLogger.logSilentHeader(i);
-                currentGentleHeaderIdx = i;
-                continue;
-            }
-
-            if (!(child instanceof ExpandableNotificationRow)) {
-                mLogger.logOther(i, child.getClass());
-                continue;
-            }
-            lastNotifIndex = i;
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            // Once we enter a new section, calculate the target position for the header.
-            switch (row.getEntry().getBucket()) {
-                case BUCKET_HEADS_UP:
-                    mLogger.logHeadsUp(i);
-                    if (showHeaders && incomingHeaderTarget == -1) {
-                        incomingHeaderTarget = i;
-                        // Offset the target if there are other headers before this that will be
-                        // moved.
-                        if (currentIncomingHeaderIdx != -1) {
-                            incomingHeaderTarget--;
-                        }
-                        if (currentMediaControlsIdx != -1) {
-                            incomingHeaderTarget--;
-                        }
-                        if (currentPeopleHeaderIdx != -1) {
-                            incomingHeaderTarget--;
-                        }
-                        if (currentAlertingHeaderIdx != -1) {
-                            incomingHeaderTarget--;
-                        }
-                        if (currentGentleHeaderIdx != -1) {
-                            incomingHeaderTarget--;
-                        }
-                    }
-                    if (mediaControlsTarget != -1) {
-                        mediaControlsTarget++;
-                    }
-                    break;
-                case BUCKET_FOREGROUND_SERVICE:
-                    mLogger.logForegroundService(i);
-                    if (mediaControlsTarget != -1) {
-                        mediaControlsTarget++;
-                    }
-                    break;
-                case BUCKET_PEOPLE:
-                    mLogger.logConversation(i);
-                    peopleNotifsPresent = true;
-                    if (showHeaders && peopleHeaderTarget == -1) {
-                        peopleHeaderTarget = i;
-                        // Offset the target if there are other headers before this that will be
-                        // moved.
-                        if (currentPeopleHeaderIdx != -1) {
-                            peopleHeaderTarget--;
-                        }
-                        if (currentAlertingHeaderIdx != -1) {
-                            peopleHeaderTarget--;
-                        }
-                        if (currentGentleHeaderIdx != -1) {
-                            peopleHeaderTarget--;
-                        }
-                    }
-                    break;
-                case BUCKET_ALERTING:
-                    mLogger.logAlerting(i);
-                    if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
-                        alertingHeaderTarget = i;
-                        // Offset the target if there are other headers before this that will be
-                        // moved.
-                        if (currentAlertingHeaderIdx != -1) {
-                            alertingHeaderTarget--;
-                        }
-                        if (currentGentleHeaderIdx != -1) {
-                            alertingHeaderTarget--;
-                        }
-                    }
-                    break;
-                case BUCKET_SILENT:
-                    mLogger.logSilent(i);
-                    if (showHeaders && gentleHeaderTarget == -1) {
-                        gentleHeaderTarget = i;
-                        // Offset the target if there are other headers before this that will be
-                        // moved.
-                        if (currentGentleHeaderIdx != -1) {
-                            gentleHeaderTarget--;
-                        }
-                    }
-                    break;
-                default:
-                    throw new IllegalStateException("Cannot find section bucket for view");
-            }
-        }
-        if (showHeaders && usingPeopleFiltering && mPeopleHubVisible && peopleHeaderTarget == -1) {
-            // Insert the people header even if there are no people visible, in order to show
-            // the hub. Put it directly above the next header.
-            if (alertingHeaderTarget != -1) {
-                peopleHeaderTarget = alertingHeaderTarget;
-            } else if (gentleHeaderTarget != -1) {
-                peopleHeaderTarget = gentleHeaderTarget;
-            } else {
-                // Put it at the end of the list.
-                peopleHeaderTarget = lastNotifIndex;
-            }
-            // Offset the target to account for the current position of the people header.
-            if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
-                peopleHeaderTarget--;
-            }
-        }
-
-        mLogger.logStr("New header target positions:");
-        mLogger.logIncomingHeader(incomingHeaderTarget);
-        mLogger.logMediaControls(mediaControlsTarget);
-        mLogger.logConversationsHeader(peopleHeaderTarget);
-        mLogger.logAlertingHeader(alertingHeaderTarget);
-        mLogger.logSilentHeader(gentleHeaderTarget);
-
-        // Add headers in reverse order to preserve indices
-        adjustHeaderVisibilityAndPosition(
-                gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
-        adjustHeaderVisibilityAndPosition(
-                alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx);
-        adjustHeaderVisibilityAndPosition(
-                peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
-        adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
-        adjustHeaderVisibilityAndPosition(incomingHeaderTarget, mIncomingHeader,
-                currentIncomingHeaderIdx);
-
-        mLogger.logStr("Final order:");
-        logShadeContents();
-        mLogger.logStr("Section boundary update complete");
-
-        // Update headers to reflect state of section contents
-        mGentleHeader.setAreThereDismissableGentleNotifs(
-                mParent.hasActiveClearableNotifications(ROWS_GENTLE));
-        mPeopleHubView.setCanSwipe(showHeaders && mPeopleHubVisible && !peopleNotifsPresent);
-        if (peopleHeaderTarget != currentPeopleHeaderIdx) {
-            mPeopleHubView.resetTranslation();
-        }
-    }
-
-    private void adjustHeaderVisibilityAndPosition(
-            int targetPosition, StackScrollerDecorView header, int currentPosition) {
-        adjustViewPosition(targetPosition, header, currentPosition);
-        if (targetPosition != -1 && currentPosition == -1) {
-            header.setContentVisible(true);
-        }
-    }
-
-    private void adjustViewPosition(int targetPosition, ExpandableView view, int currentPosition) {
-        if (targetPosition == -1) {
-            if (currentPosition != -1) {
-                mParent.removeView(view);
-            }
-        } else {
-            if (currentPosition == -1) {
-                // If the header is animating away, it will still have a parent, so detach it first
-                // TODO: We should really cancel the active animations here. This will happen
-                // automatically when the view's intro animation starts, but it's a fragile link.
-                if (view.getTransientContainer() != null) {
-                    view.getTransientContainer().removeTransientView(view);
-                    view.setTransientContainer(null);
-                }
-                mParent.addView(view, targetPosition);
-            } else {
-                mParent.changeViewPosition(view, targetPosition);
-            }
-        }
-    }
-
-    /**
-     * Updates the boundaries (as tracked by their first and last views) of the priority sections.
-     *
-     * @return {@code true} If the last view in the top section changed (so we need to animate).
-     */
-    boolean updateFirstAndLastViewsForAllSections(
-            NotificationSection[] sections,
-            List<ActivatableNotificationView> children) {
-
-        if (sections.length <= 0 || children.size() <= 0) {
-            for (NotificationSection s : sections) {
-                s.setFirstVisibleChild(null);
-                s.setLastVisibleChild(null);
-            }
-            return false;
-        }
-
-        boolean changed = false;
-        ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
-        for (NotificationSection s : sections) {
-            int filter = s.getBucket();
-            viewsInBucket.clear();
-
-            //TODO: do this in a single pass, and more better
-            for (ActivatableNotificationView v : children)  {
-                Integer bucket = getBucket(v);
-                if (bucket == null) {
-                    throw new IllegalArgumentException("Cannot find section bucket for view");
-                }
-
-                if (bucket == filter) {
-                    viewsInBucket.add(v);
-                }
-
-                if (viewsInBucket.size() >= 1) {
-                    changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
-                    changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
-                } else {
-                    changed |= s.setFirstVisibleChild(null);
-                    changed |= s.setLastVisibleChild(null);
-                }
-            }
-        }
-
-        if (DEBUG) {
-            logSections(sections);
-        }
-
-        return changed;
-    }
-
-    private void logSections(NotificationSection[] sections) {
-        for (int i = 0; i < sections.length; i++) {
-            NotificationSection s = sections[i];
-            ActivatableNotificationView first = s.getFirstVisibleChild();
-            String fs = first == null ? "(null)"
-                    :  (first instanceof ExpandableNotificationRow)
-                            ? ((ExpandableNotificationRow) first).getEntry().getKey()
-                            : Integer.toHexString(System.identityHashCode(first));
-            ActivatableNotificationView last = s.getLastVisibleChild();
-            String ls = last == null ? "(null)"
-                    :  (last instanceof ExpandableNotificationRow)
-                            ? ((ExpandableNotificationRow) last).getEntry().getKey()
-                            : Integer.toHexString(System.identityHashCode(last));
-            android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
-            android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
-        }
-    }
-
-    @VisibleForTesting
-    ExpandableView getGentleHeaderView() {
-        return mGentleHeader;
-    }
-
-    @VisibleForTesting
-    ExpandableView getAlertingHeaderView() {
-        return mAlertingHeader;
-    }
-
-    @VisibleForTesting
-    ExpandableView getPeopleHeaderView() {
-        return mPeopleHubView;
-    }
-
-    @VisibleForTesting
-    ExpandableView getMediaControlsView() {
-        return mMediaControlsView;
-    }
-
-    @VisibleForTesting
-    ExpandableView getIncomingHeaderView() {
-        return mIncomingHeader;
-    }
-
-    @VisibleForTesting
-    void setPeopleHubVisible(boolean visible) {
-        mPeopleHubVisible = visible;
-    }
-
-    private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
-        @Override
-        public void onLocaleListChanged() {
-            reinflateViews(LayoutInflater.from(mParent.getContext()));
-        }
-    };
-
-    private void onGentleHeaderClick(View v) {
-        Intent intent = new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
-        mActivityStarter.startActivity(
-                intent,
-                true,
-                true,
-                Intent.FLAG_ACTIVITY_SINGLE_TOP);
-    }
-
-    private void onClearGentleNotifsClick(View v) {
-        if (mOnClearGentleNotifsClickListener != null) {
-            mOnClearGentleNotifsClickListener.onClick(v);
-        }
-    }
-
-    void hidePeopleRow() {
-        mPeopleHubVisible = false;
-        updateSectionBoundaries("PeopleHub dismissed");
-    }
-
-    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
-     */
-    @Retention(SOURCE)
-    @IntDef(prefix = { "BUCKET_" }, value = {
-            BUCKET_HEADS_UP,
-            BUCKET_FOREGROUND_SERVICE,
-            BUCKET_MEDIA_CONTROLS,
-            BUCKET_PEOPLE,
-            BUCKET_ALERTING,
-            BUCKET_SILENT
-    })
-    public @interface PriorityBucket {}
-    public static final int BUCKET_HEADS_UP = 0;
-    public static final int BUCKET_FOREGROUND_SERVICE = 1;
-    public static final int BUCKET_MEDIA_CONTROLS = 2;
-    public static final int BUCKET_PEOPLE = 3;
-    public static final int BUCKET_ALERTING = 4;
-    public static final int BUCKET_SILENT = 5;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
new file mode 100644
index 0000000..e39a4a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.stack
+
+import android.annotation.ColorInt
+import android.annotation.IntDef
+import android.annotation.LayoutRes
+import android.content.Intent
+import android.provider.Settings
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.media.KeyguardMediaController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.people.DataListener
+import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter
+import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary
+import com.android.systemui.statusbar.notification.people.PersonViewModel
+import com.android.systemui.statusbar.notification.people.Subscription
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.children
+import com.android.systemui.util.foldToSparseArray
+import javax.inject.Inject
+
+/**
+ * Manages the boundaries of the two notification sections (high priority and low priority). Also
+ * shows/hides the headers for those sections where appropriate.
+ *
+ * TODO: Move remaining sections logic from NSSL into this class.
+ */
+class NotificationSectionsManager @Inject internal constructor(
+    private val activityStarter: ActivityStarter,
+    private val statusBarStateController: StatusBarStateController,
+    private val configurationController: ConfigurationController,
+    private val peopleHubViewAdapter: PeopleHubViewAdapter,
+    private val keyguardMediaController: KeyguardMediaController,
+    private val sectionsFeatureManager: NotificationSectionsFeatureManager,
+    private val logger: NotificationSectionsLogger
+) : SectionProvider {
+
+    private val configurationListener = object : ConfigurationController.ConfigurationListener {
+        override fun onLocaleListChanged() {
+            reinflateViews(LayoutInflater.from(parent.context))
+        }
+    }
+
+    private val peopleHubViewBoundary: PeopleHubViewBoundary = object : PeopleHubViewBoundary {
+        override fun setVisible(isVisible: Boolean) {
+            if (peopleHubVisible != isVisible) {
+                peopleHubVisible = isVisible
+                if (initialized) {
+                    updateSectionBoundaries("PeopleHub visibility changed")
+                }
+            }
+        }
+
+        override val associatedViewForClickAnimation: View
+            get() = peopleHeaderView!!
+
+        override val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
+            get() = peopleHeaderView!!.personViewAdapters
+    }
+
+    private lateinit var parent: NotificationStackScrollLayout
+    private var initialized = false
+    private var onClearSilentNotifsClickListener: View.OnClickListener? = null
+
+    @get:VisibleForTesting
+    var silentHeaderView: SectionHeaderView? = null
+        private set
+
+    @get:VisibleForTesting
+    var alertingHeaderView: SectionHeaderView? = null
+        private set
+
+    @get:VisibleForTesting
+    var incomingHeaderView: SectionHeaderView? = null
+        private set
+
+    @get:VisibleForTesting
+    var peopleHeaderView: PeopleHubView? = null
+        private set
+
+    @set:VisibleForTesting
+    var peopleHubVisible = false
+    private var peopleHubSubscription: Subscription? = null
+
+    @get:VisibleForTesting
+    var mediaControlsView: MediaHeaderView? = null
+        private set
+
+    /** Must be called before use.  */
+    fun initialize(parent: NotificationStackScrollLayout, layoutInflater: LayoutInflater) {
+        check(!initialized) { "NotificationSectionsManager already initialized" }
+        initialized = true
+        this.parent = parent
+        reinflateViews(layoutInflater)
+        configurationController.addCallback(configurationListener)
+    }
+
+    private fun <T : ExpandableView> reinflateView(
+        view: T?,
+        layoutInflater: LayoutInflater,
+        @LayoutRes layoutResId: Int
+    ): T {
+        var oldPos = -1
+        view?.let {
+            view.transientContainer?.removeView(view)
+            if (view.parent === parent) {
+                oldPos = parent.indexOfChild(view)
+                parent.removeView(view)
+            }
+        }
+        val inflated = layoutInflater.inflate(layoutResId, parent, false) as T
+        if (oldPos != -1) {
+            parent.addView(inflated, oldPos)
+        }
+        return inflated
+    }
+
+    fun createSectionsForBuckets(): Array<NotificationSection> =
+            sectionsFeatureManager.getNotificationBuckets()
+                    .map { NotificationSection(parent, it) }
+                    .toTypedArray()
+
+    /**
+     * Reinflates the entire notification header, including all decoration views.
+     */
+    fun reinflateViews(layoutInflater: LayoutInflater) {
+        silentHeaderView = reinflateView(
+                silentHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+        ).apply {
+            setHeaderText(R.string.notification_section_header_gentle)
+            setOnHeaderClickListener { onGentleHeaderClick() }
+            setOnClearAllClickListener { onClearGentleNotifsClick(it) }
+        }
+        alertingHeaderView = reinflateView(
+                alertingHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+        ).apply {
+            setHeaderText(R.string.notification_section_header_alerting)
+            setOnHeaderClickListener { onGentleHeaderClick() }
+        }
+        peopleHubSubscription?.unsubscribe()
+        peopleHubSubscription = null
+        peopleHeaderView = reinflateView(peopleHeaderView, layoutInflater, R.layout.people_strip)
+        if (ENABLE_SNOOZED_CONVERSATION_HUB) {
+            peopleHubSubscription = peopleHubViewAdapter.bindView(peopleHubViewBoundary)
+        }
+        incomingHeaderView = reinflateView(
+                incomingHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+        ).apply {
+            setHeaderText(R.string.notification_section_header_incoming)
+            setOnHeaderClickListener { onGentleHeaderClick() }
+        }
+        mediaControlsView =
+                reinflateView(mediaControlsView, layoutInflater, R.layout.keyguard_media_header)
+                        .also(keyguardMediaController::attach)
+    }
+
+    override fun beginsSection(view: View, previous: View?): Boolean =
+            view === silentHeaderView ||
+            view === mediaControlsView ||
+            view === peopleHeaderView ||
+            view === alertingHeaderView ||
+            view === incomingHeaderView ||
+            getBucket(view) != getBucket(previous)
+
+    private fun getBucket(view: View?): Int? = when {
+        view === silentHeaderView -> BUCKET_SILENT
+        view === incomingHeaderView -> BUCKET_HEADS_UP
+        view === mediaControlsView -> BUCKET_MEDIA_CONTROLS
+        view === peopleHeaderView -> BUCKET_PEOPLE
+        view === alertingHeaderView -> BUCKET_ALERTING
+        view is ExpandableNotificationRow -> view.entry.bucket
+        else -> null
+    }
+
+    private fun logShadeContents() = parent.children.forEachIndexed { i, child ->
+        when {
+            child === incomingHeaderView -> logger.logIncomingHeader(i)
+            child === mediaControlsView -> logger.logMediaControls(i)
+            child === peopleHeaderView -> logger.logConversationsHeader(i)
+            child === alertingHeaderView -> logger.logAlertingHeader(i)
+            child === silentHeaderView -> logger.logSilentHeader(i)
+            child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
+            else -> {
+                val isHeadsUp = child.isHeadsUp
+                when (child.entry.bucket) {
+                    BUCKET_HEADS_UP -> logger.logHeadsUp(i, isHeadsUp)
+                    BUCKET_PEOPLE -> logger.logConversation(i, isHeadsUp)
+                    BUCKET_ALERTING -> logger.logAlerting(i, isHeadsUp)
+                    BUCKET_SILENT -> logger.logSilent(i, isHeadsUp)
+                }
+            }
+        }
+    }
+
+    private val isUsingMultipleSections: Boolean
+        get() = sectionsFeatureManager.getNumberOfBuckets() > 1
+
+    @VisibleForTesting
+    fun updateSectionBoundaries() = updateSectionBoundaries("test")
+
+    /**
+     * Should be called whenever notifs are added, removed, or updated. Updates section boundary
+     * bookkeeping and adds/moves/removes section headers if appropriate.
+     */
+    fun updateSectionBoundaries(reason: String) {
+        if (!isUsingMultipleSections) {
+            return
+        }
+        logger.logStartSectionUpdate(reason)
+
+        // The overall strategy here is to iterate over the current children of mParent, looking
+        // for where the sections headers are currently positioned, and where each section begins.
+        // Then, once we find the start of a new section, we track that position as the "target" for
+        // the section header, adjusted for the case where existing headers are in front of that
+        // target, but won't be once they are moved / removed after the pass has completed.
+        val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD
+        val usingPeopleFiltering = sectionsFeatureManager.isFilteringEnabled()
+        val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled()
+
+        var peopleNotifsPresent = false
+        var currentMediaControlsIdx = -1
+        val mediaControlsTarget = if (usingMediaControls) 0 else -1
+        var currentIncomingHeaderIdx = -1
+        var incomingHeaderTarget = -1
+        var currentPeopleHeaderIdx = -1
+        var peopleHeaderTarget = -1
+        var currentAlertingHeaderIdx = -1
+        var alertingHeaderTarget = -1
+        var currentGentleHeaderIdx = -1
+        var gentleHeaderTarget = -1
+
+        var lastNotifIndex = 0
+        var lastIncomingIndex = -1
+        var prev: ExpandableNotificationRow? = null
+
+        for ((i, child) in parent.children.withIndex()) {
+            when {
+                // Track the existing positions of the headers
+                child === incomingHeaderView -> {
+                    logger.logIncomingHeader(i)
+                    currentIncomingHeaderIdx = i
+                }
+                child === mediaControlsView -> {
+                    logger.logMediaControls(i)
+                    currentMediaControlsIdx = i
+                }
+                child === peopleHeaderView -> {
+                    logger.logConversationsHeader(i)
+                    currentPeopleHeaderIdx = i
+                }
+                child === alertingHeaderView -> {
+                    logger.logAlertingHeader(i)
+                    currentAlertingHeaderIdx = i
+                }
+                child === silentHeaderView -> {
+                    logger.logSilentHeader(i)
+                    currentGentleHeaderIdx = i
+                }
+                child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
+                else -> {
+                    lastNotifIndex = i
+                    // Is there a section discontinuity? This usually occurs due to HUNs
+                    if (prev?.entry?.bucket?.let { it > child.entry.bucket } == true) {
+                        // Remove existing headers, and move the Incoming header if necessary
+                        if (alertingHeaderTarget != -1) {
+                            if (showHeaders && incomingHeaderTarget != -1) {
+                                incomingHeaderTarget = alertingHeaderTarget
+                            }
+                            alertingHeaderTarget = -1
+                        }
+                        if (peopleHeaderTarget != -1) {
+                            if (showHeaders && incomingHeaderTarget != -1) {
+                                incomingHeaderTarget = peopleHeaderTarget
+                            }
+                            peopleHeaderTarget = -1
+                        }
+                        if (showHeaders && incomingHeaderTarget == -1) {
+                            incomingHeaderTarget = 0
+                        }
+                        // Walk backwards changing all previous notifications to the Incoming
+                        // section
+                        for (j in i - 1 downTo lastIncomingIndex + 1) {
+                            val prevChild = parent.getChildAt(j)
+                            if (prevChild is ExpandableNotificationRow) {
+                                prevChild.entry.bucket = BUCKET_HEADS_UP
+                            }
+                        }
+                        // Track the new bottom of the Incoming section
+                        lastIncomingIndex = i - 1
+                    }
+                    val isHeadsUp = child.isHeadsUp
+                    when (child.entry.bucket) {
+                        BUCKET_FOREGROUND_SERVICE -> logger.logForegroundService(i, isHeadsUp)
+                        BUCKET_PEOPLE -> {
+                            logger.logConversation(i, isHeadsUp)
+                            peopleNotifsPresent = true
+                            if (showHeaders && peopleHeaderTarget == -1) {
+                                peopleHeaderTarget = i
+                                // Offset the target if there are other headers before this that
+                                // will be moved.
+                                if (currentPeopleHeaderIdx != -1) {
+                                    peopleHeaderTarget--
+                                }
+                                if (currentAlertingHeaderIdx != -1) {
+                                    peopleHeaderTarget--
+                                }
+                                if (currentGentleHeaderIdx != -1) {
+                                    peopleHeaderTarget--
+                                }
+                            }
+                        }
+                        BUCKET_ALERTING -> {
+                            logger.logAlerting(i, isHeadsUp)
+                            if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
+                                alertingHeaderTarget = i
+                                // Offset the target if there are other headers before this that
+                                // will be moved.
+                                if (currentAlertingHeaderIdx != -1) {
+                                    alertingHeaderTarget--
+                                }
+                                if (currentGentleHeaderIdx != -1) {
+                                    alertingHeaderTarget--
+                                }
+                            }
+                        }
+                        BUCKET_SILENT -> {
+                            logger.logSilent(i, isHeadsUp)
+                            if (showHeaders && gentleHeaderTarget == -1) {
+                                gentleHeaderTarget = i
+                                // Offset the target if there are other headers before this that
+                                // will be moved.
+                                if (currentGentleHeaderIdx != -1) {
+                                    gentleHeaderTarget--
+                                }
+                            }
+                        }
+                    }
+
+                    prev = child
+                }
+            }
+        }
+
+        if (showHeaders && usingPeopleFiltering && peopleHubVisible && peopleHeaderTarget == -1) {
+            // Insert the people header even if there are no people visible, in order to show
+            // the hub. Put it directly above the next header.
+            peopleHeaderTarget = when {
+                alertingHeaderTarget != -1 -> alertingHeaderTarget
+                gentleHeaderTarget != -1 -> gentleHeaderTarget
+                else -> lastNotifIndex // Put it at the end of the list.
+            }
+            // Offset the target to account for the current position of the people header.
+            if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
+                peopleHeaderTarget--
+            }
+        }
+
+        logger.logStr("New header target positions:")
+        logger.logIncomingHeader(incomingHeaderTarget)
+        logger.logMediaControls(mediaControlsTarget)
+        logger.logConversationsHeader(peopleHeaderTarget)
+        logger.logAlertingHeader(alertingHeaderTarget)
+        logger.logSilentHeader(gentleHeaderTarget)
+
+        // Add headers in reverse order to preserve indices
+        silentHeaderView?.let {
+            adjustHeaderVisibilityAndPosition(gentleHeaderTarget, it, currentGentleHeaderIdx)
+        }
+        alertingHeaderView?.let {
+            adjustHeaderVisibilityAndPosition(alertingHeaderTarget, it, currentAlertingHeaderIdx)
+        }
+        peopleHeaderView?.let {
+            adjustHeaderVisibilityAndPosition(peopleHeaderTarget, it, currentPeopleHeaderIdx)
+        }
+        incomingHeaderView?.let {
+            adjustHeaderVisibilityAndPosition(incomingHeaderTarget, it, currentIncomingHeaderIdx)
+        }
+        mediaControlsView?.let {
+            adjustViewPosition(mediaControlsTarget, it, currentMediaControlsIdx)
+        }
+
+        logger.logStr("Final order:")
+        logShadeContents()
+        logger.logStr("Section boundary update complete")
+
+        // Update headers to reflect state of section contents
+        silentHeaderView?.setAreThereDismissableGentleNotifs(
+                parent.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
+        )
+        peopleHeaderView?.canSwipe = showHeaders && peopleHubVisible && !peopleNotifsPresent
+        if (peopleHeaderTarget != currentPeopleHeaderIdx) {
+            peopleHeaderView?.resetTranslation()
+        }
+    }
+
+    private fun adjustHeaderVisibilityAndPosition(
+        targetPosition: Int,
+        header: StackScrollerDecorView,
+        currentPosition: Int
+    ) {
+        adjustViewPosition(targetPosition, header, currentPosition)
+        if (targetPosition != -1 && currentPosition == -1) {
+            header.isContentVisible = true
+        }
+    }
+
+    private fun adjustViewPosition(
+        targetPosition: Int,
+        view: ExpandableView,
+        currentPosition: Int
+    ) {
+        if (targetPosition == -1) {
+            if (currentPosition != -1) {
+                parent.removeView(view)
+            }
+        } else {
+            if (currentPosition == -1) {
+                // If the header is animating away, it will still have a parent, so detach it first
+                // TODO: We should really cancel the active animations here. This will happen
+                // automatically when the view's intro animation starts, but it's a fragile link.
+                view.transientContainer?.removeTransientView(view)
+                view.transientContainer = null
+                parent.addView(view, targetPosition)
+            } else {
+                parent.changeViewPosition(view, targetPosition)
+            }
+        }
+    }
+
+    private sealed class SectionBounds {
+
+        data class Many(
+            val first: ActivatableNotificationView,
+            val last: ActivatableNotificationView
+        ) : SectionBounds()
+
+        data class One(val lone: ActivatableNotificationView) : SectionBounds()
+        object None : SectionBounds()
+
+        fun addNotif(notif: ActivatableNotificationView): SectionBounds = when (this) {
+            is None -> One(notif)
+            is One -> Many(lone, notif)
+            is Many -> copy(last = notif)
+        }
+
+        fun updateSection(section: NotificationSection): Boolean = when (this) {
+            is None -> section.setFirstAndLastVisibleChildren(null, null)
+            is One -> section.setFirstAndLastVisibleChildren(lone, lone)
+            is Many -> section.setFirstAndLastVisibleChildren(first, last)
+        }
+
+        private fun NotificationSection.setFirstAndLastVisibleChildren(
+            first: ActivatableNotificationView?,
+            last: ActivatableNotificationView?
+        ): Boolean {
+            val firstChanged = setFirstVisibleChild(first)
+            val lastChanged = setLastVisibleChild(last)
+            return firstChanged || lastChanged
+        }
+    }
+
+    /**
+     * Updates the boundaries (as tracked by their first and last views) of the priority sections.
+     *
+     * @return `true` If the last view in the top section changed (so we need to animate).
+     */
+    fun updateFirstAndLastViewsForAllSections(
+        sections: Array<NotificationSection>,
+        children: List<ActivatableNotificationView>
+    ): Boolean {
+        // Create mapping of bucket to section
+        val sectionBounds = children.asSequence()
+                // Group children by bucket
+                .groupingBy {
+                    getBucket(it)
+                            ?: throw IllegalArgumentException("Cannot find section bucket for view")
+                }
+                // Combine each bucket into a SectionBoundary
+                .foldToSparseArray(
+                        SectionBounds.None,
+                        size = sections.size,
+                        operation = SectionBounds::addNotif
+                )
+        // Update each section with the associated boundary, tracking if there was a change
+        val changed = sections.fold(false) { changed, section ->
+            val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
+            bounds.updateSection(section) || changed
+        }
+        if (DEBUG) {
+            logSections(sections)
+        }
+        return changed
+    }
+
+    private fun logSections(sections: Array<NotificationSection>) {
+        for (i in sections.indices) {
+            val s = sections[i]
+            val fs = when (val first = s.firstVisibleChild) {
+                null -> "(null)"
+                is ExpandableNotificationRow -> first.entry.key
+                else -> Integer.toHexString(System.identityHashCode(first))
+            }
+            val ls = when (val last = s.lastVisibleChild) {
+                null -> "(null)"
+                is ExpandableNotificationRow -> last.entry.key
+                else -> Integer.toHexString(System.identityHashCode(last))
+            }
+            Log.d(TAG, "updateSections: f=$fs s=$i")
+            Log.d(TAG, "updateSections: l=$ls s=$i")
+        }
+    }
+
+    private fun onGentleHeaderClick() {
+        val intent = Intent(Settings.ACTION_NOTIFICATION_SETTINGS)
+        activityStarter.startActivity(
+                intent,
+                true,
+                true,
+                Intent.FLAG_ACTIVITY_SINGLE_TOP)
+    }
+
+    private fun onClearGentleNotifsClick(v: View) {
+        onClearSilentNotifsClickListener?.onClick(v)
+    }
+
+    /** Listener for when the "clear all" button is clicked on the gentle notification header. */
+    fun setOnClearSilentNotifsClickListener(listener: View.OnClickListener) {
+        onClearSilentNotifsClickListener = listener
+    }
+
+    fun hidePeopleRow() {
+        peopleHubVisible = false
+        updateSectionBoundaries("PeopleHub dismissed")
+    }
+
+    fun setHeaderForegroundColor(@ColorInt color: Int) {
+        peopleHeaderView?.setTextColor(color)
+        silentHeaderView?.setForegroundColor(color)
+        alertingHeaderView?.setForegroundColor(color)
+    }
+
+    companion object {
+        private const val TAG = "NotifSectionsManager"
+        private const val DEBUG = false
+        private const val ENABLE_SNOOZED_CONVERSATION_HUB = false
+    }
+}
+
+/**
+ * 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
+ */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+        prefix = ["BUCKET_"],
+        value = [
+            BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
+            BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
+        ]
+)
+annotation class PriorityBucket
+
+const val BUCKET_UNKNOWN = 0
+const val BUCKET_MEDIA_CONTROLS = 1
+const val BUCKET_HEADS_UP = 2
+const val BUCKET_FOREGROUND_SERVICE = 3
+const val BUCKET_PEOPLE = 4
+const val BUCKET_ALERTING = 5
+const val BUCKET_SILENT = 6
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 1ccc2bd..3db4b6f 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
@@ -21,7 +21,7 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -577,7 +577,7 @@
 
         mSectionsManager = notificationSectionsManager;
         mSectionsManager.initialize(this, LayoutInflater.from(context));
-        mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
+        mSectionsManager.setOnClearSilentNotifsClickListener(v -> {
             // Leave the shade open if there will be other notifs left over to clear
             final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
             clearNotifications(ROWS_GENTLE, closeShade);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index d0a872e..80785db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -455,7 +454,7 @@
      * If there is a {@link NotificationGroup} associated with the provided entry, this method
      * will update the suppression of that group.
      */
-    public void updateSuppression(@NonNull final NotificationEntry entry) {
+    public void updateSuppression(NotificationEntry entry) {
         NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
         if (group != null) {
             updateSuppression(group);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 926c1bd..2e4a929d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -321,7 +321,8 @@
                 || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
                 || state.mHeadsUpShowing
                 || state.mScrimsVisibility != ScrimController.TRANSPARENT)
-                || state.mBackgroundBlurRadius > 0;
+                || state.mBackgroundBlurRadius > 0
+                || state.mLaunchingActivity;
     }
 
     private void applyFitsSystemWindows(State state) {
@@ -485,6 +486,11 @@
         apply(mCurrentState);
     }
 
+    void setLaunchingActivity(boolean launching) {
+        mCurrentState.mLaunchingActivity = launching;
+        apply(mCurrentState);
+    }
+
     public void setScrimsVisibility(int scrimsVisibility) {
         mCurrentState.mScrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
@@ -645,6 +651,7 @@
         boolean mForceCollapsed;
         boolean mForceDozeBrightness;
         boolean mForceUserActivity;
+        boolean mLaunchingActivity;
         boolean mBackdropShowing;
         boolean mWallpaperSupportsAmbientMode;
         boolean mNotTouchable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 596a607..42222d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -93,6 +93,7 @@
     private PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarTransitions mBarTransitions;
     private StatusBar mService;
+    private NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
     private boolean mDoubleTapEnabled;
     private boolean mSingleTapEnabled;
@@ -429,11 +430,19 @@
     }
 
     public void setExpandAnimationPending(boolean pending) {
-        mExpandAnimationPending = pending;
+        if (mExpandAnimationPending != pending) {
+            mExpandAnimationPending = pending;
+            mNotificationShadeWindowController
+                    .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
+        }
     }
 
     public void setExpandAnimationRunning(boolean running) {
-        mExpandAnimationRunning = running;
+        if (mExpandAnimationRunning != running) {
+            mExpandAnimationRunning = running;
+            mNotificationShadeWindowController
+                    .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
+        }
     }
 
     public void cancelExpandHelper() {
@@ -456,8 +465,9 @@
         }
     }
 
-    public void setService(StatusBar statusBar) {
+    public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
         mService = statusBar;
+        mNotificationShadeWindowController = controller;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
index ba32327..de9c745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
@@ -37,8 +37,9 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -60,7 +61,7 @@
 
     private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
 
-    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
     private final ViewRippler mViewRippler = new ViewRippler();
 
     private @StyleRes int mStyleRes;
@@ -323,7 +324,7 @@
     }
 
     private void onRotateSuggestionClick(View v) {
-        mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
+        mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
         incrementNumAcceptedRotationSuggestionsIfNeeded();
         setRotationLockedAtAngle(mLastRotationSuggestion);
     }
@@ -345,7 +346,7 @@
     private void showAndLogRotationSuggestion() {
         setRotateSuggestionButtonState(true /* visible */);
         rescheduleRotationTimeout(false /* reasonHover */);
-        mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
+        mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN);
     }
 
     private boolean shouldOverrideUserLockPrefs(final int rotation) {
@@ -474,4 +475,19 @@
             }
         };
     }
+
+    enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The rotation button was shown")
+        ROTATION_SUGGESTION_SHOWN(206),
+        @UiEvent(doc = "The rotation button was clicked")
+        ROTATION_SUGGESTION_ACCEPTED(207);
+
+        private final int mId;
+        RotationButtonEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
 }
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 bbf83bc..19de191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1001,7 +1001,7 @@
         updateTheme();
 
         inflateStatusBarWindow();
-        mNotificationShadeWindowViewController.setService(this);
+        mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
         mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
@@ -1488,7 +1488,7 @@
         if (mDividerOptional.isPresent()) {
             divider = mDividerOptional.get();
         }
-        if (divider == null || !divider.inSplitMode()) {
+        if (divider == null || !divider.isDividerVisible()) {
             final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
             if (navbarPos == NAV_BAR_POS_INVALID) {
                 return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 44ece35..7924348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -429,7 +429,9 @@
             }
             int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                     null, null, getActivityOptions(adapter));
-            mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
+            mMainThreadHandler.post(() -> {
+                mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
+            });
         } catch (RemoteException | PendingIntent.CanceledException e) {
             // the stack trace isn't very helpful here.
             // Just log the exception message.
@@ -449,10 +451,11 @@
                                 mActivityLaunchAnimator.getLaunchAnimation(
                                         row, mStatusBar.isOccluded())),
                                 new UserHandle(UserHandle.getUserId(appUid)));
-                mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */);
 
                 // Putting it back on the main thread, since we're touching views
                 mMainThreadHandler.post(() -> {
+                    mActivityLaunchAnimator.setLaunchResult(launchResult,
+                            true /* isActivityIntent */);
                     removeHUN(row);
                 });
                 if (shouldCollapse()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 428de9e..669e6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -37,6 +37,7 @@
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -73,6 +74,7 @@
     private View mPendingRemoteInputView;
     private KeyguardManager mKeyguardManager;
     private final CommandQueue mCommandQueue;
+    private final ActionClickLogger mActionClickLogger;
     private int mDisabled2;
     protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
     private Handler mMainHandler = new Handler();
@@ -87,7 +89,8 @@
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ActivityStarter activityStarter, ShadeController shadeController,
-            CommandQueue commandQueue) {
+            CommandQueue commandQueue,
+            ActionClickLogger clickLogger) {
         mContext = context;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mShadeController = shadeController;
@@ -101,6 +104,7 @@
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mCommandQueue = commandQueue;
         mCommandQueue.addCallback(this);
+        mActionClickLogger = clickLogger;
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
         mGroupManager = groupManager;
         // Listen to onKeyguardShowingChanged in case a managed profile needs to be unlocked
@@ -304,9 +308,12 @@
             NotificationRemoteInputManager.ClickHandler defaultHandler) {
         final boolean isActivity = pendingIntent.isActivity();
         if (isActivity) {
+            mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
             final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
                     pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
             mActivityStarter.dismissKeyguardThenExecute(() -> {
+                mActionClickLogger.logKeyguardGone(pendingIntent);
+
                 try {
                     ActivityManager.getService().resumeAppSwitches();
                 } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
new file mode 100644
index 0000000..c91033e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.view.ViewGroup
+
+/** [Sequence] that yields all of the direct children of this [ViewGroup] */
+val ViewGroup.children
+    get() = sequence {
+        for (i in 0 until childCount) yield(getChildAt(i))
+    }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
new file mode 100644
index 0000000..accb81e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.util.SparseArray
+
+/**
+ * Transforms an [Array] into a [SparseArray], by applying each element to [keySelector] in order to
+ * generate the index at which it will be placed. If two elements produce the same index, the latter
+ * replaces the former in the final result.
+ *
+ * See [Array.associateBy].
+ */
+inline fun <T> Array<T>.associateByToSparseArray(
+    crossinline keySelector: (T) -> Int
+): SparseArray<T> {
+    val sparseArray = SparseArray<T>(size)
+    for (value in this) {
+        sparseArray.put(keySelector(value), value)
+    }
+    return sparseArray
+}
+
+/**
+ * Folds a [Grouping] into a [SparseArray]. See [Grouping.fold].
+ */
+inline fun <T, R> Grouping<T, Int>.foldToSparseArray(
+    initial: R,
+    size: Int = -1,
+    crossinline operation: (R, T) -> R
+): SparseArray<R> {
+    val sparseArray = when {
+        size < 0 -> SparseArray<R>()
+        else -> SparseArray<R>(size)
+    }
+    sourceIterator().forEach { elem ->
+        val key = keyOf(elem)
+        val acc = sparseArray.get(key) ?: initial
+        sparseArray.put(key, operation(acc, elem))
+    }
+    return sparseArray
+}
+
+/**
+ * Wraps this [SparseArray] into an immutable [Map], the methods of which forward to this
+ * [SparseArray].
+ */
+fun <T> SparseArray<T>.asMap(): Map<Int, T> = SparseArrayMapWrapper(this)
+
+private class SparseArrayMapWrapper<T>(
+    private val sparseArray: SparseArray<T>
+) : Map<Int, T> {
+
+    private data class Entry<T>(override val key: Int, override val value: T) : Map.Entry<Int, T>
+
+    private val entrySequence = sequence {
+        val size = sparseArray.size()
+        for (i in 0 until size) {
+            val key = sparseArray.keyAt(i)
+            val value = sparseArray.get(key)
+            yield(Entry(key, value))
+        }
+    }
+
+    override val entries: Set<Map.Entry<Int, T>>
+        get() = object : Set<Map.Entry<Int, T>> {
+            override val size: Int
+                get() = this@SparseArrayMapWrapper.size
+
+            override fun contains(element: Map.Entry<Int, T>): Boolean =
+                    sparseArray[element.key]?.let { it == element.value } == true
+
+            override fun containsAll(elements: Collection<Map.Entry<Int, T>>): Boolean =
+                    elements.all { contains(it) }
+
+            override fun isEmpty(): Boolean = size == 0
+
+            override fun iterator(): Iterator<Map.Entry<Int, T>> = entrySequence.iterator()
+        }
+
+    override val keys: Set<Int> = object : Set<Int> {
+        private val keySequence = entrySequence.map { it.key }
+
+        override val size: Int
+            get() = this@SparseArrayMapWrapper.size
+
+        override fun contains(element: Int): Boolean = containsKey(element)
+
+        override fun containsAll(elements: Collection<Int>): Boolean =
+                elements.all { contains(it) }
+
+        override fun isEmpty(): Boolean = size == 0
+
+        override fun iterator(): Iterator<Int> = keySequence.iterator()
+    }
+    override val size: Int
+        get() = sparseArray.size()
+    override val values: Collection<T>
+        get() = object : Collection<T> {
+            private val valueSequence = entrySequence.map { it.value }
+
+            override val size: Int
+                get() = this@SparseArrayMapWrapper.size
+
+            override fun contains(element: T): Boolean = containsValue(element)
+
+            override fun containsAll(elements: Collection<T>): Boolean =
+                    elements.all { contains(it) }
+
+            override fun isEmpty(): Boolean = this@SparseArrayMapWrapper.isEmpty()
+
+            override fun iterator(): Iterator<T> = valueSequence.iterator()
+        }
+
+    override fun containsKey(key: Int): Boolean = sparseArray.contains(key)
+
+    override fun containsValue(value: T): Boolean = sparseArray.indexOfValue(value) >= 0
+
+    override fun get(key: Int): T? = sparseArray.get(key)
+
+    override fun isEmpty(): Boolean = sparseArray.size() == 0
+}
\ No newline at end of file
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 52923ab..96e868d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -317,7 +317,7 @@
         verify(mNotificationEntryManager).updateNotifications(any());
 
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
         verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
 
@@ -329,7 +329,7 @@
         mBubbleController.updateBubble(mRow2.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
 
         Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getEntry().getKey());
         assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b));
@@ -350,10 +350,9 @@
         mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */
                 false, /* showInShade */ true);
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
 
-        mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
         verify(mNotificationEntryManager, times(1)).performRemoveNotification(
                 eq(mRow.getEntry().getSbn()), anyInt());
         assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
@@ -366,7 +365,7 @@
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_CHANGED);
+                mRow.getEntry(), BubbleController.DISMISS_USER_CHANGED);
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 eq(mRow.getEntry().getSbn()), anyInt());
         assertFalse(mBubbleController.hasBubbles());
@@ -564,8 +563,7 @@
 
         // Dismiss currently expanded
         mBubbleController.removeBubble(
-                mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
-                        .getEntry().getKey(),
+                mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
                 BubbleController.DISMISS_USER_GESTURE);
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
 
@@ -576,8 +574,7 @@
 
         // Dismiss that one
         mBubbleController.removeBubble(
-                mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
-                        .getEntry().getKey(),
+                mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
                 BubbleController.DISMISS_USER_GESTURE);
 
         // Make sure state changes and collapse happens
@@ -703,7 +700,7 @@
     @Test
     public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
-        mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED);
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
         verify(mDeleteIntent, never()).send();
     }
 
@@ -711,7 +708,7 @@
     public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(1)).send();
     }
 
@@ -811,7 +808,7 @@
 
         // Dismiss the bubble into overflow.
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mBubbleController.hasBubbles());
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
@@ -832,7 +829,7 @@
                 mRow.getEntry()));
 
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_NO_LONGER_BUBBLE);
+                mRow.getEntry(), BubbleController.DISMISS_NO_LONGER_BUBBLE);
         assertFalse(mBubbleController.hasBubbles());
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
@@ -854,12 +851,12 @@
 
         mBubbleData.setMaxOverflowBubbles(1);
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertEquals(mBubbleData.getBubbles().size(), 2);
         assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
 
         mBubbleController.removeBubble(
-                mRow2.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow2.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         // Overflow max of 1 is reached; mRow is oldest, so it gets removed
         verify(mNotificationEntryManager, times(1)).performRemoveNotification(
                 mRow.getEntry().getSbn(), REASON_CANCEL);
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 882504b..66f119a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -20,8 +20,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -177,8 +180,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
 
         // Verify
         verifyUpdateReceived();
@@ -295,14 +297,12 @@
         mBubbleData.setListener(mListener);
 
         mBubbleData.setMaxOverflowBubbles(1);
-        mBubbleData.notificationEntryRemoved(
-                mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
 
         // Overflow max of 1 is reached; A1 is oldest, so it gets removed
-        mBubbleData.notificationEntryRemoved(
-                mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
     }
@@ -449,8 +449,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
     }
@@ -470,8 +469,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryB1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOrderNotChanged();
     }
@@ -491,8 +489,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
         verifyUpdateReceived();
         assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2);
     }
@@ -513,14 +510,12 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA2.getKey(), BubbleController.DISMISS_GROUP_CANCELLED);
+        mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of());
     }
@@ -539,8 +534,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA2.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+        mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL);
         verifyUpdateReceived();
         assertSelectionChangedTo(mBubbleB2);
     }
@@ -631,8 +625,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
 
         // Verify the selection was cleared.
         verifyUpdateReceived();
@@ -786,8 +779,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryB2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1);
     }
@@ -812,13 +804,11 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertSelectionChangedTo(mBubbleA1);
 
-        mBubbleData.notificationEntryRemoved(
-                mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertSelectionChangedTo(mBubbleB1);
     }
@@ -933,8 +923,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
-                mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertExpandedChangedTo(false);
     }
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 c16801c..73b8760 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -285,8 +285,7 @@
         assertTrue(mBubbleController.hasBubbles());
         verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
 
-        mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
         verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
     }
@@ -303,8 +302,7 @@
         mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
 
         // Now remove the bubble
-        mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
 
         // We don't remove the notification since the bubble is still in overflow.
@@ -324,8 +322,7 @@
         mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
 
         // Now remove the bubble
-        mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
         assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
 
         // Since the notif is dismissed and not in overflow, once the bubble is removed,
@@ -505,8 +502,7 @@
 
         // Dismiss currently expanded
         mBubbleController.removeBubble(
-                mBubbleData.getBubbleInStackWithKey(
-                        stackView.getExpandedBubble().getKey()).getEntry().getKey(),
+                mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
                 BubbleController.DISMISS_USER_GESTURE);
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
 
@@ -517,8 +513,7 @@
 
         // Dismiss that one
         mBubbleController.removeBubble(
-                mBubbleData.getBubbleInStackWithKey(
-                        stackView.getExpandedBubble().getKey()).getEntry().getKey(),
+                mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
                 BubbleController.DISMISS_USER_GESTURE);
 
         // Make sure state changes and collapse happens
@@ -618,7 +613,7 @@
     @Test
     public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
-        mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED);
+        mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
         verify(mDeleteIntent, never()).send();
     }
 
@@ -626,7 +621,7 @@
     public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(1)).send();
     }
 
@@ -691,7 +686,7 @@
 
         // Dismiss the bubble
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
+                mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mBubbleController.hasBubbles());
 
         // Dismiss the notification
@@ -712,7 +707,7 @@
 
         // Dismiss the bubble
         mBubbleController.removeBubble(
-                mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
+                mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
         assertFalse(mBubbleController.hasBubbles());
 
         // Dismiss the notification
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
new file mode 100644
index 0000000..d49d021
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.bubbles.storage
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubblePersistentRepositoryTest : SysuiTestCase() {
+
+    private val bubbles = listOf(
+            BubbleEntity(0, "com.example.messenger", "shortcut-1"),
+            BubbleEntity(10, "com.example.chat", "alice and bob"),
+            BubbleEntity(0, "com.example.messenger", "shortcut-2")
+    )
+    private lateinit var repository: BubblePersistentRepository
+
+    @Before
+    fun setup() {
+        repository = BubblePersistentRepository(mContext)
+    }
+
+    @Test
+    fun testReadWriteOperation() {
+        repository.persistsToDisk(bubbles)
+        assertEquals(bubbles, repository.readFromDisk())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
new file mode 100644
index 0000000..7acc937
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.bubbles.storage
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleVolatileRepositoryTest : SysuiTestCase() {
+
+    private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1")
+    private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob")
+    private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2")
+    private val bubbles = listOf(bubble1, bubble2, bubble3)
+
+    private lateinit var repository: BubbleVolatileRepository
+
+    @Before
+    fun setup() {
+        repository = BubbleVolatileRepository()
+    }
+
+    @Test
+    fun testAddAndRemoveBubbles() {
+        repository.addBubbles(bubbles)
+        assertEquals(bubbles, repository.bubbles)
+        repository.addBubbles(listOf(bubble1))
+        assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles)
+        repository.removeBubbles(listOf(bubble3))
+        assertEquals(listOf(bubble2, bubble1), repository.bubbles)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
new file mode 100644
index 0000000..ef4580c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.bubbles.storage
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleXmlHelperTest : SysuiTestCase() {
+
+    private val bubbles = listOf(
+        BubbleEntity(0, "com.example.messenger", "shortcut-1"),
+        BubbleEntity(10, "com.example.chat", "alice and bob"),
+        BubbleEntity(0, "com.example.messenger", "shortcut-2")
+    )
+
+    @Test
+    fun testWriteXml() {
+        val expectedEntries = """
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" />
+            <bb uid="10" pkg="com.example.chat" sid="alice and bob" />
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" />
+        """.trimIndent()
+        ByteArrayOutputStream().use {
+            writeXml(it, bubbles)
+            val actual = it.toString()
+            assertTrue("cannot find expected entry in \n$actual",
+                    actual.contains(expectedEntries))
+        }
+    }
+
+    @Test
+    fun testReadXml() {
+        val src = """
+            <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+            <bs>
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" />
+            <bb uid="10" pkg="com.example.chat" sid="alice and bob" />
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" />
+            </bs>
+        """.trimIndent()
+        val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
+        assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index ee7733a..4a85980 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -53,6 +53,7 @@
 import com.android.systemui.controls.controller.ControlsController;
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.statusbar.BlurUtils;
@@ -107,6 +108,7 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private RingerModeTracker mRingerModeTracker;
     @Mock private RingerModeLiveData mRingerModeLiveData;
+    @Mock private SysUiState mSysUiState;
     @Mock private Handler mHandler;
 
     private TestableLooper mTestableLooper;
@@ -150,6 +152,7 @@
                 mControlsController,
                 mUiEventLogger,
                 mRingerModeTracker,
+                mSysUiState,
                 mHandler
         );
         mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
new file mode 100644
index 0000000..7678525
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.FrameLayout
+
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for PlayerViewHolder.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class PlayerViewHolderTest : SysuiTestCase() {
+
+    private lateinit var inflater: LayoutInflater
+    private lateinit var parent: ViewGroup
+
+    @Before
+    fun setUp() {
+        inflater = LayoutInflater.from(context)
+        parent = FrameLayout(context)
+    }
+
+    @Test
+    fun create() {
+        val holder = PlayerViewHolder.create(inflater, parent)
+        assertThat(holder.player).isNotNull()
+    }
+
+    @Test
+    fun backgroundIsIlluminationDrawable() {
+        val holder = PlayerViewHolder.create(inflater, parent)
+        assertThat(holder.background.background as IlluminationDrawable).isNotNull()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index e5ced0d..1bbf24f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.media
 
-import android.graphics.Color
 import android.media.MediaMetadata
 import android.media.session.MediaController
 import android.media.session.PlaybackState
@@ -355,25 +354,6 @@
     }
 
     @Test
-    fun taskUpdatesProgress() {
-        // GIVEN that the PlaybackState contins the initial position
-        val initialPosition = 0L
-        val state = PlaybackState.Builder().run {
-            setState(PlaybackState.STATE_PLAYING, initialPosition, 1f)
-            build()
-        }
-        whenever(mockController.getPlaybackState()).thenReturn(state)
-        viewModel.updateController(mockController)
-        // WHEN the task runs
-        with(fakeExecutor) {
-            advanceClockToNext()
-            runAllReady()
-        }
-        // THEN elapsed time has increased
-        assertThat(viewModel.progress.value!!.elapsedTime).isGreaterThan(initialPosition.toInt())
-    }
-
-    @Test
     fun startListeningQueuesPollTask() {
         // GIVEN not listening
         viewModel.listening = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d124bad..a24fa84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -20,9 +20,9 @@
 import static android.content.Intent.ACTION_USER_SWITCHED;
 import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 1117646..5a7dea4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -82,7 +82,8 @@
                 () -> mock(StatusBar.class),
                 mStateController,
                 Handler.createAsync(Looper.myLooper()),
-                mRemoteInputUriController);
+                mRemoteInputUriController,
+                mock(ActionClickLogger.class));
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
                 .setOpPkg(TEST_PACKAGE_NAME)
@@ -256,17 +257,26 @@
 
     private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
 
-        TestableNotificationRemoteInputManager(Context context,
+        TestableNotificationRemoteInputManager(
+                Context context,
                 NotificationLockscreenUserManager lockscreenUserManager,
                 SmartReplyController smartReplyController,
                 NotificationEntryManager notificationEntryManager,
                 Lazy<StatusBar> statusBarLazy,
                 StatusBarStateController statusBarStateController,
                 Handler mainHandler,
-                RemoteInputUriController remoteInputUriController) {
-            super(context, lockscreenUserManager, smartReplyController, notificationEntryManager,
-                    statusBarLazy, statusBarStateController, mainHandler,
-                    remoteInputUriController);
+                RemoteInputUriController remoteInputUriController,
+                ActionClickLogger actionClickLogger) {
+            super(
+                    context,
+                    lockscreenUserManager,
+                    smartReplyController,
+                    notificationEntryManager,
+                    statusBarLazy,
+                    statusBarStateController,
+                    mainHandler,
+                    remoteInputUriController,
+                    actionClickLogger);
         }
 
         public void setUpWithPresenterForTest(Callback callback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 22dc080..79507e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -92,7 +92,8 @@
                 mNotificationEntryManager, () -> mock(StatusBar.class),
                 mStatusBarStateController,
                 Handler.createAsync(Looper.myLooper()),
-                mRemoteInputUriController);
+                mRemoteInputUriController,
+                mock(ActionClickLogger.class));
         mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
         mNotification = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index b4cabfd..9f47f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -21,6 +21,7 @@
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.app.NotificationManager.IMPORTANCE_HIGH
 import android.app.NotificationManager.IMPORTANCE_LOW
+import android.os.SystemClock
 import android.service.notification.NotificationListenerService.RankingMap
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
@@ -36,8 +37,8 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.phone.NotificationGroupManager
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import dagger.Lazy
@@ -58,17 +59,19 @@
     private lateinit var personNotificationIdentifier: PeopleNotificationIdentifier
     private lateinit var rankingManager: TestableNotificationRankingManager
     private lateinit var sectionsManager: NotificationSectionsFeatureManager
+    private lateinit var notificationFilter: NotificationFilter
 
     @Before
     fun setup() {
         personNotificationIdentifier =
                 mock(PeopleNotificationIdentifier::class.java)
         sectionsManager = mock(NotificationSectionsFeatureManager::class.java)
+        notificationFilter = mock(NotificationFilter::class.java)
         rankingManager = TestableNotificationRankingManager(
                 lazyMedia,
                 mock(NotificationGroupManager::class.java),
                 mock(HeadsUpManager::class.java),
-                mock(NotificationFilter::class.java),
+                notificationFilter,
                 mock(NotificationEntryManagerLogger::class.java),
                 sectionsManager,
                 personNotificationIdentifier,
@@ -324,6 +327,32 @@
         assertEquals(e.bucket, BUCKET_SILENT)
     }
 
+    @Test
+    fun testFilter_resetsInitalizationTime() {
+        // GIVEN an entry that was initialized 1 second ago
+        val notif = Notification.Builder(mContext, "test") .build()
+
+        val e = NotificationEntryBuilder()
+            .setPkg("pkg")
+            .setOpPkg("pkg")
+            .setTag("tag")
+            .setNotification(notif)
+            .setUser(mContext.user)
+            .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
+            .setOverrideGroupKey("")
+            .build()
+
+        e.setInitializationTime(SystemClock.elapsedRealtime() - 1000)
+        assertEquals(true, e.hasFinishedInitialization())
+
+        // WHEN we update ranking and filter out the notification entry
+        whenever(notificationFilter.shouldFilterOut(e)).thenReturn(true)
+        rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
+
+        // THEN the initialization time for the entry is reset
+        assertEquals(false, e.hasFinishedInitialization())
+    }
+
     internal class TestableNotificationRankingManager(
         mediaManager: Lazy<NotificationMediaManager>,
         groupManager: NotificationGroupManager,
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 3adc3d0..a93b54a 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
@@ -19,8 +19,10 @@
 import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -33,6 +35,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
@@ -475,6 +478,28 @@
     }
 
     @Test
+    public void testFilter_resetsInitalizationTime() {
+        // GIVEN a NotifFilter that filters out a specific package
+        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
+        mListBuilder.addFinalizeFilter(filter1);
+
+        // GIVEN a notification that was initialized 1 second ago that will be filtered out
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(PACKAGE_1)
+                .setId(nextId(PACKAGE_1))
+                .setRank(nextRank())
+                .build();
+        entry.setInitializationTime(SystemClock.elapsedRealtime() - 1000);
+        assertTrue(entry.hasFinishedInitialization());
+
+        // WHEN the pipeline is kicked off
+        mReadyForBuildListener.onBuildList(Arrays.asList(entry));
+
+        // THEN the entry's initialization time is reset
+        assertFalse(entry.hasFinishedInitialization());
+    }
+
+    @Test
     public void testNotifFiltersCanBePreempted() {
         // GIVEN two notif filters
         NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index d39b2c2..a3a46f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.logging;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 546bce8..3dc941a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,11 +18,11 @@
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -52,6 +52,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -135,140 +136,152 @@
     @Test
     public void testInsertHeader() {
         // GIVEN a stack with HI and LO rows but no section headers
-        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+        setStackState(
+                ALERTING,
+                ALERTING,
+                ALERTING,
+                GENTLE);
 
         // WHEN we update the section headers
         mSectionsManager.updateSectionBoundaries();
 
         // THEN a LO section header is added
-        verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
+        verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
     }
 
     @Test
     public void testRemoveHeader() {
         // GIVEN a stack that originally had a header between the HI and LO sections
-        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+        setStackState(
+                ALERTING,
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         // WHEN the last LO row is replaced with a HI row
         setStackState(
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.GENTLE_HEADER,
-                ChildType.ALERTING);
+                ALERTING,
+                ALERTING,
+                GENTLE_HEADER,
+                ALERTING);
         clearInvocations(mNssl);
         mSectionsManager.updateSectionBoundaries();
 
         // THEN the LO section header is removed
-        verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
+        verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
     }
 
     @Test
     public void testDoNothingIfHeaderAlreadyRemoved() {
         // GIVEN a stack with only HI rows
-        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING);
+        setStackState(
+                ALERTING,
+                ALERTING,
+                ALERTING);
 
         // WHEN we update the sections headers
         mSectionsManager.updateSectionBoundaries();
 
         // THEN we don't add any section headers
-        verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
+        verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
     }
 
     @Test
     public void testMoveHeaderForward() {
         // GIVEN a stack that originally had a header between the HI and LO sections
         setStackState(
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.GENTLE);
+                ALERTING,
+                ALERTING,
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         // WHEN the LO section moves forward
         setStackState(
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.GENTLE,
-                ChildType.GENTLE_HEADER,
-                ChildType.GENTLE);
+                ALERTING,
+                ALERTING,
+                GENTLE,
+                GENTLE_HEADER,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         // THEN the LO section header is also moved forward
-        verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
+        verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
     }
 
     @Test
     public void testMoveHeaderBackward() {
         // GIVEN a stack that originally had a header between the HI and LO sections
         setStackState(
-                ChildType.ALERTING,
-                ChildType.GENTLE,
-                ChildType.GENTLE,
-                ChildType.GENTLE);
+                ALERTING,
+                GENTLE,
+                GENTLE,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         // WHEN the LO section moves backward
         setStackState(
-                ChildType.ALERTING,
-                ChildType.GENTLE_HEADER,
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.GENTLE);
+                ALERTING,
+                GENTLE_HEADER,
+                ALERTING,
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         // THEN the LO section header is also moved backward (with appropriate index shifting)
-        verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 3);
+        verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 3);
     }
 
     @Test
     public void testHeaderRemovedFromTransientParent() {
         // GIVEN a stack where the header is animating away
         setStackState(
-                ChildType.ALERTING,
-                ChildType.GENTLE,
-                ChildType.GENTLE,
-                ChildType.GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-        setStackState(
-                ChildType.ALERTING,
-                ChildType.GENTLE_HEADER);
+                ALERTING,
+                GENTLE_HEADER);
         mSectionsManager.updateSectionBoundaries();
         clearInvocations(mNssl);
 
         ViewGroup transientParent = mock(ViewGroup.class);
-        mSectionsManager.getGentleHeaderView().setTransientContainer(transientParent);
+        mSectionsManager.getSilentHeaderView().setTransientContainer(transientParent);
 
         // WHEN the LO section reappears
         setStackState(
-                ChildType.ALERTING,
-                ChildType.GENTLE);
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         // THEN the header is first removed from the transient parent before being added to the
         // NSSL.
-        verify(transientParent).removeTransientView(mSectionsManager.getGentleHeaderView());
-        verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 1);
+        verify(transientParent).removeTransientView(mSectionsManager.getSilentHeaderView());
+        verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 1);
     }
 
     @Test
     public void testHeaderNotShownOnLockscreen() {
         // GIVEN a stack of HI and LO notifs on the lockscreen
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+        setStackState(
+                ALERTING,
+                ALERTING,
+                ALERTING,
+                GENTLE);
 
         // WHEN we update the section headers
         mSectionsManager.updateSectionBoundaries();
 
         // Then the section header is not added
-        verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
+        verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
     }
 
     @Test
     public void testHeaderShownWhenEnterLockscreen() {
         // GIVEN a stack of HI and LO notifs on the lockscreen
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+        setStackState(
+                ALERTING,
+                ALERTING,
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         // WHEN we unlock
@@ -276,20 +289,23 @@
         mSectionsManager.updateSectionBoundaries();
 
         // Then the section header is added
-        verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
+        verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
     }
 
     @Test
     public void testHeaderHiddenWhenEnterLockscreen() {
         // GIVEN a stack of HI and LO notifs on the shade
-        setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
+        setStackState(
+                ALERTING,
+                GENTLE_HEADER,
+                GENTLE);
 
         // WHEN we go back to the keyguard
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
         mSectionsManager.updateSectionBoundaries();
 
         // Then the section header is removed
-        verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
+        verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
     }
 
     @Test
@@ -297,13 +313,13 @@
         enablePeopleFiltering();
 
         setStackState(
-                ChildType.GENTLE_HEADER,
-                ChildType.PERSON,
-                ChildType.ALERTING,
-                ChildType.GENTLE);
+                GENTLE_HEADER,
+                PERSON,
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
-        verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
+        verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
         verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
         verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
     }
@@ -313,12 +329,12 @@
         enablePeopleFiltering();
 
         setStackState(
-                ChildType.PERSON,
-                ChildType.ALERTING,
-                ChildType.GENTLE);
+                PERSON,
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
-        verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 2);
+        verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2);
         verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
         verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
     }
@@ -328,15 +344,15 @@
         enablePeopleFiltering();
 
         setStackState(
-                ChildType.PEOPLE_HEADER,
-                ChildType.ALERTING_HEADER,
-                ChildType.GENTLE_HEADER,
-                ChildType.PERSON,
-                ChildType.ALERTING,
-                ChildType.GENTLE);
+                PEOPLE_HEADER,
+                ALERTING_HEADER,
+                GENTLE_HEADER,
+                PERSON,
+                ALERTING,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
-        verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 4);
+        verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 4);
         verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2);
         verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
     }
@@ -347,12 +363,11 @@
         enablePeopleFiltering();
 
         setStackState(
-                ChildType.PEOPLE_HEADER,
-                ChildType.ALERTING_HEADER,
-                ChildType.ALERTING,
-                ChildType.GENTLE_HEADER,
-                ChildType.GENTLE
-        );
+                PEOPLE_HEADER,
+                ALERTING_HEADER,
+                ALERTING,
+                GENTLE_HEADER,
+                GENTLE);
         mSectionsManager.updateSectionBoundaries();
 
         verify(mNssl, never()).removeView(mSectionsManager.getPeopleHeaderView());
@@ -360,16 +375,16 @@
     }
 
     @Test
-    public void testPeopleFiltering_HunWhilePeopleVisible() {
+    public void testPeopleFiltering_AlertingHunWhilePeopleVisible() {
         enablePeopleFiltering();
 
         setupMockStack(
-                ChildType.PEOPLE_HEADER,
-                ChildType.HEADS_UP,
-                ChildType.PERSON,
-                ChildType.ALERTING_HEADER,
-                ChildType.GENTLE_HEADER,
-                ChildType.GENTLE
+                PEOPLE_HEADER,
+                ALERTING.headsUp(),
+                PERSON,
+                ALERTING_HEADER,
+                GENTLE_HEADER,
+                GENTLE
         );
         mSectionsManager.updateSectionBoundaries();
 
@@ -384,17 +399,74 @@
     }
 
     @Test
+    public void testPeopleFiltering_PersonHunWhileAlertingHunVisible() {
+        enablePeopleFiltering();
+
+        setupMockStack(
+                PERSON.headsUp(),
+                INCOMING_HEADER,
+                ALERTING.headsUp(),
+                PEOPLE_HEADER,
+                PERSON
+        );
+        mSectionsManager.updateSectionBoundaries();
+
+        verifyMockStack(
+                ChildType.INCOMING_HEADER,
+                ChildType.HEADS_UP,
+                ChildType.HEADS_UP,
+                ChildType.PEOPLE_HEADER,
+                ChildType.PERSON
+        );
+    }
+
+    @Test
+    public void testPeopleFiltering_PersonHun() {
+        enablePeopleFiltering();
+
+        setupMockStack(
+                PERSON.headsUp(),
+                PEOPLE_HEADER,
+                PERSON
+        );
+        mSectionsManager.updateSectionBoundaries();
+
+        verifyMockStack(
+                ChildType.PEOPLE_HEADER,
+                ChildType.PERSON,
+                ChildType.PERSON
+        );
+    }
+
+    @Test
+    public void testPeopleFiltering_AlertingHunWhilePersonHunning() {
+        enablePeopleFiltering();
+
+        setupMockStack(
+                ALERTING.headsUp(),
+                PERSON.headsUp()
+        );
+        mSectionsManager.updateSectionBoundaries();
+        verifyMockStack(
+                ChildType.INCOMING_HEADER,
+                ChildType.HEADS_UP,
+                ChildType.PEOPLE_HEADER,
+                ChildType.PERSON
+        );
+    }
+
+    @Test
     public void testPeopleFiltering_Fsn() {
         enablePeopleFiltering();
 
         setupMockStack(
-                ChildType.INCOMING_HEADER,
-                ChildType.HEADS_UP,
-                ChildType.PEOPLE_HEADER,
-                ChildType.FSN,
-                ChildType.PERSON,
-                ChildType.ALERTING,
-                ChildType.GENTLE
+                INCOMING_HEADER,
+                ALERTING.headsUp(),
+                PEOPLE_HEADER,
+                FSN,
+                PERSON,
+                ALERTING,
+                GENTLE
         );
         mSectionsManager.updateSectionBoundaries();
 
@@ -416,7 +488,7 @@
         enableMediaControls();
 
         // GIVEN a stack that doesn't include media controls
-        setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
+        setStackState(ALERTING, GENTLE_HEADER, GENTLE);
 
         // WHEN we go back to the keyguard
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
@@ -431,14 +503,20 @@
         enableMediaControls();
 
         // GIVEN a stack that doesn't include media controls but includes HEADS_UP
-        setupMockStack(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
-                ChildType.GENTLE);
+        setupMockStack(
+                ALERTING.headsUp(),
+                ALERTING,
+                GENTLE_HEADER,
+                GENTLE);
 
         // WHEN we go back to the keyguard
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
         mSectionsManager.updateSectionBoundaries();
 
-        verifyMockStack(ChildType.HEADS_UP, ChildType.MEDIA_CONTROLS, ChildType.ALERTING,
+        verifyMockStack(
+                ChildType.MEDIA_CONTROLS,
+                ChildType.ALERTING,
+                ChildType.ALERTING,
                 ChildType.GENTLE);
     }
 
@@ -455,11 +533,12 @@
         FSN, PERSON, ALERTING, GENTLE, OTHER
     }
 
-    private void setStackState(ChildType... children) {
+    private void setStackState(StackEntry... children) {
         when(mNssl.getChildCount()).thenReturn(children.length);
         for (int i = 0; i < children.length; i++) {
             View child;
-            switch (children[i]) {
+            StackEntry entry = children[i];
+            switch (entry.mChildType) {
                 case INCOMING_HEADER:
                     child = mSectionsManager.getIncomingHeaderView();
                     break;
@@ -473,22 +552,19 @@
                     child = mSectionsManager.getAlertingHeaderView();
                     break;
                 case GENTLE_HEADER:
-                    child = mSectionsManager.getGentleHeaderView();
-                    break;
-                case HEADS_UP:
-                    child = mockNotification(BUCKET_HEADS_UP);
+                    child = mSectionsManager.getSilentHeaderView();
                     break;
                 case FSN:
-                    child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+                    child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
                     break;
                 case PERSON:
-                    child = mockNotification(BUCKET_PEOPLE);
+                    child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
                     break;
                 case ALERTING:
-                    child = mockNotification(BUCKET_ALERTING);
+                    child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
                     break;
                 case GENTLE:
-                    child = mockNotification(BUCKET_SILENT);
+                    child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
                     break;
                 case OTHER:
                     child = mock(View.class);
@@ -503,12 +579,24 @@
         }
     }
 
-    private View mockNotification(int bucket) {
-        ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
-                RETURNS_DEEP_STUBS);
+    private View mockNotification(int bucket, boolean headsUp) {
+        ExpandableNotificationRow notifRow =
+                mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
         when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
-        when(notifRow.getEntry().getBucket()).thenReturn(bucket);
         when(notifRow.getParent()).thenReturn(mNssl);
+
+        NotificationEntry mockEntry = mock(NotificationEntry.class);
+        when(notifRow.getEntry()).thenReturn(mockEntry);
+
+        int[] bucketRef = new int[] { bucket };
+        when(mockEntry.getBucket()).thenAnswer(invocation -> bucketRef[0]);
+        doAnswer(invocation -> {
+            bucketRef[0] = invocation.getArgument(0);
+            return null;
+        }).when(mockEntry).setBucket(anyInt());
+
+        when(notifRow.isHeadsUp()).thenReturn(headsUp);
+        when(mockEntry.isRowHeadsUp()).thenReturn(headsUp);
         return notifRow;
     }
 
@@ -533,7 +621,7 @@
                 actual.add(ChildType.ALERTING_HEADER);
                 continue;
             }
-            if (child == mSectionsManager.getGentleHeaderView()) {
+            if (child == mSectionsManager.getSilentHeaderView()) {
                 actual.add(ChildType.GENTLE_HEADER);
                 continue;
             }
@@ -565,7 +653,7 @@
         assertThat(actual).containsExactly((Object[]) expected).inOrder();
     }
 
-    private void setupMockStack(ChildType... childTypes) {
+    private void setupMockStack(StackEntry... entries) {
         final List<View> children = new ArrayList<>();
         when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
         when(mNssl.getChildAt(anyInt()))
@@ -590,9 +678,9 @@
             children.add(newIndex, child);
             return null;
         }).when(mNssl).changeViewPosition(any(), anyInt());
-        for (ChildType childType : childTypes) {
+        for (StackEntry entry : entries) {
             View child;
-            switch (childType) {
+            switch (entry.mChildType) {
                 case INCOMING_HEADER:
                     child = mSectionsManager.getIncomingHeaderView();
                     break;
@@ -606,22 +694,19 @@
                     child = mSectionsManager.getAlertingHeaderView();
                     break;
                 case GENTLE_HEADER:
-                    child = mSectionsManager.getGentleHeaderView();
-                    break;
-                case HEADS_UP:
-                    child = mockNotification(BUCKET_HEADS_UP);
+                    child = mSectionsManager.getSilentHeaderView();
                     break;
                 case FSN:
-                    child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+                    child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
                     break;
                 case PERSON:
-                    child = mockNotification(BUCKET_PEOPLE);
+                    child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
                     break;
                 case ALERTING:
-                    child = mockNotification(BUCKET_ALERTING);
+                    child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
                     break;
                 case GENTLE:
-                    child = mockNotification(BUCKET_SILENT);
+                    child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
                     break;
                 case OTHER:
                     child = mock(View.class);
@@ -629,9 +714,48 @@
                     when(child.getParent()).thenReturn(mNssl);
                     break;
                 default:
-                    throw new RuntimeException("Unknown ChildType: " + childType);
+                    throw new RuntimeException("Unknown ChildType: " + entry.mChildType);
             }
             children.add(child);
         }
     }
+
+    private static final StackEntry INCOMING_HEADER = new StackEntry(ChildType.INCOMING_HEADER);
+    private static final StackEntry MEDIA_CONTROLS = new StackEntry(ChildType.MEDIA_CONTROLS);
+    private static final StackEntry PEOPLE_HEADER = new StackEntry(ChildType.PEOPLE_HEADER);
+    private static final StackEntry ALERTING_HEADER = new StackEntry(ChildType.ALERTING_HEADER);
+    private static final StackEntry GENTLE_HEADER = new StackEntry(ChildType.GENTLE_HEADER);
+    private static final StackEntry FSN = new StackEntry(ChildType.FSN);
+    private static final StackEntry.Hunnable PERSON = new StackEntry.Hunnable(ChildType.PERSON);
+    private static final StackEntry.Hunnable ALERTING = new StackEntry.Hunnable(ChildType.ALERTING);
+    private static final StackEntry GENTLE = new StackEntry(ChildType.GENTLE);
+
+    private static class StackEntry {
+        final ChildType mChildType;
+        final boolean mIsHeadsUp;
+
+        StackEntry(ChildType childType) {
+            this(childType, false);
+        }
+
+        StackEntry(ChildType childType, boolean isHeadsUp) {
+            mChildType = childType;
+            mIsHeadsUp = isHeadsUp;
+        }
+
+        static class Hunnable extends StackEntry {
+
+            Hunnable(ChildType childType) {
+                super(childType, false);
+            }
+
+            Hunnable(ChildType childType, boolean isHeadsUp) {
+                super(childType, isHeadsUp);
+            }
+
+            public Hunnable headsUp() {
+                return new Hunnable(mChildType, true);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index cc2d1c2..e04d25b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -83,6 +83,7 @@
     @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
     @Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
+    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
 
     @Before
     public void setUp() {
@@ -121,7 +122,7 @@
                 mNotificationPanelViewController,
                 mStatusBarViewFactory);
         mController.setupExpandedStatusBar();
-        mController.setService(mStatusBar);
+        mController.setService(mStatusBar, mNotificationShadeWindowController);
         mController.setDragDownHelper(mDragDownHelper);
 
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index cd2c349..bf2bd38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -31,6 +31,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -73,7 +74,8 @@
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
                 mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
                 mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
-                mActivityStarter, mShadeController, new CommandQueue(mContext)));
+                mActivityStarter, mShadeController, new CommandQueue(mContext),
+                mock(ActionClickLogger.class)));
         mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e9cf727..b3867a3 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1212,7 +1212,7 @@
         final Intent intent = new Intent();
         final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
         intent.setComponent(name);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         try {
             mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
         } catch (ActivityNotFoundException ignore) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 930f124..37f1ad1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2098,6 +2098,7 @@
             }
             ServiceManager.addService("permission", new PermissionController(this));
             ServiceManager.addService("processinfo", new ProcessInfoService(this));
+            ServiceManager.addService("cacheinfo", new CacheBinder(this));
 
             ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
                     "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);
@@ -2191,16 +2192,18 @@
 
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                Process.enableFreezer(false);
-            }
+            try {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(false);
+                }
 
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
-                    "meminfo", pw)) return;
-            PriorityDump.dump(mPriorityDumper, fd, pw, args);
-
-            if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                Process.enableFreezer(true);
+                if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+                        "meminfo", pw)) return;
+                PriorityDump.dump(mPriorityDumper, fd, pw, args);
+            } finally {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(true);
+                }
             }
         }
     }
@@ -2213,16 +2216,18 @@
 
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                Process.enableFreezer(false);
-            }
+            try {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(false);
+                }
 
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
-                    "gfxinfo", pw)) return;
-            mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
-
-            if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                Process.enableFreezer(true);
+                if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+                        "gfxinfo", pw)) return;
+                mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
+            } finally {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(true);
+                }
             }
         }
     }
@@ -2235,16 +2240,18 @@
 
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                Process.enableFreezer(false);
-            }
+            try {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(false);
+                }
 
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
-                    "dbinfo", pw)) return;
-            mActivityManagerService.dumpDbInfo(fd, pw, args);
-
-            if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                Process.enableFreezer(true);
+                if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+                        "dbinfo", pw)) return;
+                mActivityManagerService.dumpDbInfo(fd, pw, args);
+            } finally {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(true);
+                }
             }
         }
     }
@@ -2280,6 +2287,34 @@
         }
     }
 
+    static class CacheBinder extends Binder {
+        ActivityManagerService mActivityManagerService;
+
+        CacheBinder(ActivityManagerService activityManagerService) {
+            mActivityManagerService = activityManagerService;
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            try {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(false);
+                }
+
+                if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+                        "cacheinfo", pw)) {
+                    return;
+                }
+
+                mActivityManagerService.dumpBinderCacheContents(fd, pw, args);
+            } finally {
+                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+                    Process.enableFreezer(true);
+                }
+            }
+        }
+    }
+
     public static final class Lifecycle extends SystemService {
         private final ActivityManagerService mService;
         private static ActivityTaskManagerService sAtm;
@@ -11083,18 +11118,22 @@
 
     void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc, String prefix) {
         pw.print(prefix);
-        pw.print("#");
+        pw.print('#');
+        if (index < 10) {
+            pw.print(' ');
+        }
         pw.print(index);
         pw.print(": ");
         pw.print(ProcessList.makeOomAdjString(proc.setAdj, false));
-        pw.print(" ");
+        pw.print(' ');
         pw.print(ProcessList.makeProcStateString(proc.getCurProcState()));
-        pw.print(" ");
+        pw.print(' ');
+        ActivityManager.printCapabilitiesSummary(pw, proc.curCapability);
+        pw.print(' ');
         pw.print(proc.toShortString());
-        pw.print(" ");
         if (proc.hasActivitiesOrRecentTasks() || proc.hasClientActivities()
                 || proc.treatLikeActivity) {
-            pw.print(" activity=");
+            pw.print(" act:");
             boolean printed = false;
             if (proc.hasActivities()) {
                 pw.print("activities");
@@ -12559,7 +12598,7 @@
             char schedGroup;
             switch (r.setSchedGroup) {
                 case ProcessList.SCHED_GROUP_BACKGROUND:
-                    schedGroup = 'B';
+                    schedGroup = 'b';
                     break;
                 case ProcessList.SCHED_GROUP_DEFAULT:
                     schedGroup = 'F';
@@ -12570,6 +12609,9 @@
                 case ProcessList.SCHED_GROUP_RESTRICTED:
                     schedGroup = 'R';
                     break;
+                case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
+                    schedGroup = 'B';
+                    break;
                 default:
                     schedGroup = '?';
                     break;
@@ -12597,7 +12639,10 @@
             pw.print(foreground);
             pw.print('/');
             pw.print(procState);
-            pw.print(" trm:");
+            pw.print(' ');
+            ActivityManager.printCapabilitiesSummary(pw, r.curCapability);
+            pw.print(' ');
+            pw.print(" t:");
             if (r.trimMemoryLevel < 10) pw.print(' ');
             pw.print(r.trimMemoryLevel);
             pw.print(' ');
@@ -12712,6 +12757,39 @@
         }
     }
 
+    final void dumpBinderCacheContents(FileDescriptor fd, PrintWriter pw, String[] args) {
+        ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, false, args);
+        if (procs == null) {
+            pw.println("No process found for: " + args[0]);
+            return;
+        }
+
+        pw.println("Per-process Binder Cache Contents");
+
+        for (int i = procs.size() - 1; i >= 0; i--) {
+            ProcessRecord r = procs.get(i);
+            if (r.thread != null) {
+                pw.println("\n\n** Cache info for pid " + r.pid + " [" + r.processName + "] **");
+                pw.flush();
+                try {
+                    TransferPipe tp = new TransferPipe();
+                    try {
+                        r.thread.dumpCacheInfo(tp.getWriteFd(), args);
+                        tp.go(fd);
+                    } finally {
+                        tp.kill();
+                    }
+                } catch (IOException e) {
+                    pw.println("Failure while dumping the app " + r);
+                    pw.flush();
+                } catch (RemoteException e) {
+                    pw.println("Got a RemoteException while dumping the app " + r);
+                    pw.flush();
+                }
+            }
+        }
+    }
+
     final void dumpDbInfo(FileDescriptor fd, PrintWriter pw, String[] args) {
         ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, false, args);
         if (procs == null) {
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
deleted file mode 100644
index 0e34801..0000000
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowInsets;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-
-/**
- * Dialog to show when a user switch it about to happen for the car. The intent is to snapshot the
- * screen immediately after the dialog shows so that the user is informed that something is
- * happening in the background rather than just freeze the screen and not know if the user-switch
- * affordance was being handled.
- */
-final class CarUserSwitchingDialog extends UserSwitchingDialog {
-
-    private static final String TAG = "ActivityManagerCarUserSwitchingDialog";
-    private View mView;
-
-    public CarUserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
-            UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage,
-            String switchingToSystemUserMessage) {
-        super(service, context, oldUser, newUser, aboveSystem, switchingFromSystemUserMessage,
-                switchingToSystemUserMessage);
-    }
-
-    @Override
-    void inflateContent() {
-        // Set up the dialog contents
-        setCancelable(false);
-        Resources res = getContext().getResources();
-        // Custom view due to alignment and font size requirements
-        getContext().setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert_UserSwitchingDialog);
-        mView = LayoutInflater.from(getContext()).inflate(
-                R.layout.car_user_switching_dialog,
-                null);
-
-        UserManager userManager =
-                (UserManager) getContext().getSystemService(Context.USER_SERVICE);
-        Bitmap bitmap = userManager.getUserIcon(mNewUser.id);
-        if (bitmap != null) {
-            CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(bitmap,
-                    res.getDimension(R.dimen.car_fullscreen_user_pod_image_avatar_height));
-            ((ImageView) mView.findViewById(R.id.user_loading_avatar))
-                    .setImageDrawable(drawable);
-        }
-
-        TextView msgView = mView.findViewById(R.id.user_loading);
-
-        // TODO(b/145132885): use constant from CarSettings
-        boolean showInfo = "true".equals(Settings.Global.getString(
-                getContext().getContentResolver(),
-                "android.car.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE"));
-
-        if (showInfo) {
-            msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
-                    + mOldUser.id + " to " + mNewUser.id + ")");
-        } else {
-            msgView.setText(res.getString(R.string.car_loading_profile));
-        }
-        setView(mView);
-    }
-
-    @Override
-    public void show() {
-        super.show();
-        hideNavigationBar();
-    }
-
-    private void hideNavigationBar() {
-        mView.getWindowInsetsController().hide(WindowInsets.Type.navigationBars());
-    }
-
-    /**
-     * Converts the user icon to a circularly clipped one.  This is used in the User Picker and
-     * Settings.
-     */
-    static class CircleFramedDrawable extends Drawable {
-
-        private final Bitmap mBitmap;
-        private final int mSize;
-        private final Paint mPaint;
-
-        private float mScale;
-        private Rect mSrcRect;
-        private RectF mDstRect;
-
-        public static CircleFramedDrawable getInstance(Bitmap icon, float iconSize) {
-            CircleFramedDrawable instance = new CircleFramedDrawable(icon, (int) iconSize);
-            return instance;
-        }
-
-        public CircleFramedDrawable(Bitmap icon, int size) {
-            super();
-            mSize = size;
-
-            mBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
-            final Canvas canvas = new Canvas(mBitmap);
-
-            final int width = icon.getWidth();
-            final int height = icon.getHeight();
-            final int square = Math.min(width, height);
-
-            final Rect cropRect = new Rect((width - square) / 2, (height - square) / 2,
-                    square, square);
-            final RectF circleRect = new RectF(0f, 0f, mSize, mSize);
-
-            final Path fillPath = new Path();
-            fillPath.addArc(circleRect, 0f, 360f);
-
-            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-
-            // opaque circle
-            mPaint = new Paint();
-            mPaint.setAntiAlias(true);
-            mPaint.setColor(Color.BLACK);
-            mPaint.setStyle(Paint.Style.FILL);
-            canvas.drawPath(fillPath, mPaint);
-
-            // mask in the icon where the bitmap is opaque
-            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
-            canvas.drawBitmap(icon, cropRect, circleRect, mPaint);
-
-            // prepare paint for frame drawing
-            mPaint.setXfermode(null);
-
-            mScale = 1f;
-
-            mSrcRect = new Rect(0, 0, mSize, mSize);
-            mDstRect = new RectF(0, 0, mSize, mSize);
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            final float inside = mScale * mSize;
-            final float pad = (mSize - inside) / 2f;
-
-            mDstRect.set(pad, pad, mSize - pad, mSize - pad);
-            canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null);
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.TRANSLUCENT;
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-            // Needed to implement abstract method.  Do nothing.
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-            // Needed to implement abstract method.  Do nothing.
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ad85853..c13bb5a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -151,15 +151,6 @@
     @EnabledAfter(targetSdkVersion=android.os.Build.VERSION_CODES.Q)
     static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L;
 
-    // TODO: remove this when development is done.
-    // These are debug flags used between OomAdjuster and AppOpsService to detect and report absence
-    // of the real flags.
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q = 1 << 27;
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q = 1 << 28;
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
-
     /**
      * For some direct access we need to power manager.
      */
@@ -1506,8 +1497,9 @@
                     //lost the capability, use temp location capability to mark this case.
                     //TODO: remove this block when development is done.
                     capabilityFromFGS |=
-                            (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
-                                    != 0 ? DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
+                            (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0
+                                    ? ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION
+                                    : 0;
                 }
                 if (s.mAllowWhileInUsePermissionInFgs) {
                     boolean enabled = false;
@@ -1520,22 +1512,22 @@
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
                                         != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
-                                        : DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
+                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
                                         != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
-                                        : DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
                     } else {
                         // Remove fgsType check and assign PROCESS_CAPABILITY_FOREGROUND_CAMERA
                         // and MICROPHONE when finish debugging.
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
                                         != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
-                                        : DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
+                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
                                         != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
-                                        : DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
+                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b753de9..108fb7d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -972,7 +972,7 @@
             return buildOomTag("vis", "vis", "   ", setAdj,
                     ProcessList.VISIBLE_APP_ADJ, compact);
         } else if (setAdj >= ProcessList.FOREGROUND_APP_ADJ) {
-            return buildOomTag("fore  ", "fore", null, setAdj,
+            return buildOomTag("fg ", "fg ", "   ", setAdj,
                     ProcessList.FOREGROUND_APP_ADJ, compact);
         } else if (setAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
             return buildOomTag("psvc  ", "psvc", null, setAdj,
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index fc6931d..c5152c0 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -431,16 +431,46 @@
                 pw.print(" nextPssTime=");
                 TimeUtils.formatDuration(nextPssTime, nowUptime, pw);
                 pw.println();
-        pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
-                pw.print(" lruSeq="); pw.print(lruSeq);
-                pw.print(" lastPss="); DebugUtils.printSizeValue(pw, lastPss*1024);
-                pw.print(" lastSwapPss="); DebugUtils.printSizeValue(pw, lastSwapPss*1024);
-                pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss*1024);
-                pw.print(" lastCachedSwapPss="); DebugUtils.printSizeValue(pw, lastCachedSwapPss*1024);
-        pw.print(" lastRss="); DebugUtils.printSizeValue(pw, mLastRss * 1024);
+        pw.print(prefix); pw.print("lastPss="); DebugUtils.printSizeValue(pw, lastPss * 1024);
+                pw.print(" lastSwapPss="); DebugUtils.printSizeValue(pw, lastSwapPss * 1024);
+                pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss * 1024);
+                pw.print(" lastCachedSwapPss="); DebugUtils.printSizeValue(pw,
+                        lastCachedSwapPss * 1024);
+                pw.print(" lastRss="); DebugUtils.printSizeValue(pw, mLastRss * 1024);
                 pw.println();
         pw.print(prefix); pw.print("procStateMemTracker: ");
         procStateMemTracker.dumpLine(pw);
+        pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
+                pw.print(" lruSeq="); pw.println(lruSeq);
+        pw.print(prefix); pw.print("oom adj: max="); pw.print(maxAdj);
+                pw.print(" curRaw="); pw.print(mCurRawAdj);
+                pw.print(" setRaw="); pw.print(setRawAdj);
+                pw.print(" cur="); pw.print(curAdj);
+                pw.print(" set="); pw.println(setAdj);
+        pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
+                pw.print(" lastCompactAction="); pw.println(lastCompactAction);
+        pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
+                pw.print(" setSchedGroup="); pw.print(setSchedGroup);
+                pw.print(" systemNoUi="); pw.print(systemNoUi);
+                pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
+        pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
+                pw.print(" mRepProcState="); pw.print(mRepProcState);
+                pw.print(" pssProcState="); pw.print(pssProcState);
+                pw.print(" setProcState="); pw.print(setProcState);
+                pw.print(" lastStateTime=");
+                TimeUtils.formatDuration(lastStateTime, nowUptime, pw);
+                pw.println();
+        pw.print(prefix); pw.print("curCapability=");
+                ActivityManager.printCapabilitiesFull(pw, curCapability);
+                pw.print(" setCapability=");
+                ActivityManager.printCapabilitiesFull(pw, setCapability);
+                pw.println();
+        if (hasShownUi || mPendingUiClean || hasAboveClient || treatLikeActivity) {
+            pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
+                    pw.print(" pendingUiClean="); pw.print(mPendingUiClean);
+                    pw.print(" hasAboveClient="); pw.print(hasAboveClient);
+                    pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
+        }
         pw.print(prefix); pw.print("cached="); pw.print(mCached);
                 pw.print(" empty="); pw.println(empty);
         if (serviceb) {
@@ -451,32 +481,6 @@
             pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(notCachedSinceIdle);
                     pw.print(" initialIdlePss="); pw.println(initialIdlePss);
         }
-        pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj);
-                pw.print(" curRaw="); pw.print(mCurRawAdj);
-                pw.print(" setRaw="); pw.print(setRawAdj);
-                pw.print(" cur="); pw.print(curAdj);
-                pw.print(" set="); pw.println(setAdj);
-        pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
-                pw.print(" lastCompactAction="); pw.print(lastCompactAction);
-        pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
-                pw.print(" setSchedGroup="); pw.print(setSchedGroup);
-                pw.print(" systemNoUi="); pw.print(systemNoUi);
-                pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
-        pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
-                pw.print(" mRepProcState="); pw.print(mRepProcState);
-                pw.print(" pssProcState="); pw.print(pssProcState);
-                pw.print(" setProcState="); pw.print(setProcState);
-                pw.print(" curCapability="); pw.print(curCapability);
-                pw.print(" setCapability="); pw.print(setCapability);
-                pw.print(" lastStateTime=");
-                TimeUtils.formatDuration(lastStateTime, nowUptime, pw);
-                pw.println();
-        if (hasShownUi || mPendingUiClean || hasAboveClient || treatLikeActivity) {
-            pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
-                    pw.print(" pendingUiClean="); pw.print(mPendingUiClean);
-                    pw.print(" hasAboveClient="); pw.print(hasAboveClient);
-                    pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
-        }
         if (connectionService != null || connectionGroup != 0) {
             pw.print(prefix); pw.print("connectionGroup="); pw.print(connectionGroup);
             pw.print(" Importance="); pw.print(connectionImportance);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5b12933..fac4a1e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2736,19 +2736,13 @@
 
         void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
                 String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
-            Dialog d;
             if (!mService.mContext.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-                d = new UserSwitchingDialog(mService, mService.mContext, fromUser, toUser,
-                    true /* above system */, switchingFromSystemUserMessage,
-                    switchingToSystemUserMessage);
-            } else {
-                d = new CarUserSwitchingDialog(mService, mService.mContext, fromUser, toUser,
-                    true /* above system */, switchingFromSystemUserMessage,
-                    switchingToSystemUserMessage);
+                final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
+                        toUser, true /* above system */, switchingFromSystemUserMessage,
+                        switchingToSystemUserMessage);
+                d.show();
             }
-
-            d.show();
         }
 
         void reportGlobalUsageEventLocked(int event) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5ebfb00..ae38e81 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -66,11 +66,11 @@
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
-import static com.android.server.am.OomAdjuster.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
 import static java.lang.Long.max;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 3ee3d50..df4c269 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -36,6 +36,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
@@ -43,6 +44,9 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
 
 /** @hide */
 /*package*/ final class AudioDeviceBroker {
@@ -91,6 +95,9 @@
     // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
     /*package*/ final Object mSetModeLock = new Object();
 
+    /** PID of current audio mode owner communicated by AudioService */
+    private int mModeOwnerPid = 0;
+
     //-------------------------------------------------------------------
     /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
         mContext = context;
@@ -136,6 +143,7 @@
     /*package*/ void onSystemReady() {
         synchronized (mSetModeLock) {
             synchronized (mDeviceStateLock) {
+                mModeOwnerPid = mAudioService.getModeOwnerPid();
                 mBtHelper.onSystemReady();
             }
         }
@@ -202,30 +210,57 @@
      * @param eventSource for logging purposes
      * @return true if speakerphone state changed
      */
-    /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) {
+    /*package*/ boolean setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
         synchronized (mDeviceStateLock) {
-            final boolean wasOn = isSpeakerphoneOn();
-            if (on) {
-                if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-                    setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
-                }
-                mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
-            } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
-                if (mBtHelper.isBluetoothScoOn()) {
-                    mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
-                    setForceUse_Async(
-                            AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
-                } else {
-                    mForcedUseForComm = AudioSystem.FORCE_NONE;
-                }
+            if (!addSpeakerphoneClient(cb, pid, on)) {
+                return false;
             }
-
-            mForcedUseForCommExt = mForcedUseForComm;
-            setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+            final boolean wasOn = isSpeakerphoneOn();
+            updateSpeakerphoneOn(eventSource);
             return (wasOn != isSpeakerphoneOn());
         }
     }
 
+    @GuardedBy("mDeviceStateLock")
+    private void updateSpeakerphoneOn(String eventSource) {
+        if (isSpeakerphoneOnRequested()) {
+            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+                setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+            }
+            mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+        } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+            if (mBtHelper.isBluetoothScoOn()) {
+                mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+                setForceUse_Async(
+                        AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
+            } else {
+                mForcedUseForComm = AudioSystem.FORCE_NONE;
+            }
+        }
+        mForcedUseForCommExt = mForcedUseForComm;
+        setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+    }
+
+    /**
+     * Returns if speakerphone is requested ON or OFF.
+     * If the current audio mode owner is in the speakerphone client list, use this preference.
+     * Otherwise use first client's preference (first client corresponds to latest request).
+     * Speakerphone is requested OFF if no client is in the list.
+     * @return true if speakerphone is requested ON, false otherwise
+     */
+    @GuardedBy("mDeviceStateLock")
+    private boolean isSpeakerphoneOnRequested() {
+        if (mSpeakerphoneClients.isEmpty()) {
+            return false;
+        }
+        for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+            if (cl.getPid() == mModeOwnerPid) {
+                return cl.isOn();
+            }
+        }
+        return mSpeakerphoneClients.get(0).isOn();
+    }
+
     /*package*/ boolean isSpeakerphoneOn() {
         synchronized (mDeviceStateLock) {
             return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
@@ -384,7 +419,8 @@
                 }
                 mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
             } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-                mForcedUseForComm = AudioSystem.FORCE_NONE;
+                mForcedUseForComm = isSpeakerphoneOnRequested()
+                        ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE;
             }
             mForcedUseForCommExt = mForcedUseForComm;
             AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
@@ -429,8 +465,8 @@
         sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
     }
 
-    /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
-        sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+    /*package*/ void postSetModeOwnerPid(int pid) {
+        sendIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid);
     }
 
     /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -483,7 +519,7 @@
     }
 
     /*package*/ int getModeOwnerPid() {
-        return mAudioService.getModeOwnerPid();
+        return mModeOwnerPid;
     }
 
     /*package*/ int getDeviceForStream(int streamType) {
@@ -605,6 +641,10 @@
         sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
     }
 
+    /*package*/ void postSpeakerphoneClientDied(Object obj) {
+        sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj);
+    }
+
     /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy,
                                                            AudioDeviceAttributes device)
     {
@@ -674,7 +714,7 @@
                 new BtHelper.BluetoothA2dpDeviceInfo(btDevice);
         return (mBrokerHandler.hasEqualMessages(
                     MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, devInfoToCheck)
-                || mBrokerHandler.hasEqualMessages(
+            || mBrokerHandler.hasEqualMessages(
                     MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, devInfoToCheck));
     }
 
@@ -707,7 +747,20 @@
         } else {
             pw.println("Message handler is null");
         }
+
         mDeviceInventory.dump(pw, prefix);
+
+        pw.println("\n" + prefix + "mForcedUseForComm: "
+                +  AudioSystem.forceUseConfigToString(mForcedUseForComm));
+        pw.println(prefix + "mForcedUseForCommExt: "
+                + AudioSystem.forceUseConfigToString(mForcedUseForCommExt));
+        pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid);
+        pw.println(prefix + "Speakerphone clients:");
+        mSpeakerphoneClients.forEach((cl) -> {
+            pw.println("  " + prefix + "pid: " + cl.getPid() + " on: "
+                        + cl.isOn() + " cb: " + cl.getBinder()); });
+
+        mBtHelper.dump(pw, prefix);
     }
 
     //---------------------------------------------------------------------
@@ -877,10 +930,16 @@
                         mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
                     }
                     break;
-                case MSG_I_DISCONNECT_BT_SCO:
+                case MSG_I_SET_MODE_OWNER_PID:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            mBtHelper.disconnectBluetoothSco(msg.arg1);
+                            if (mModeOwnerPid != msg.arg1) {
+                                mModeOwnerPid = msg.arg1;
+                                updateSpeakerphoneOn("setNewModeOwner");
+                                if (mModeOwnerPid != 0) {
+                                    mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
+                                }
+                            }
                         }
                     }
                     break;
@@ -891,6 +950,11 @@
                         }
                     }
                     break;
+                case MSG_L_SPEAKERPHONE_CLIENT_DIED:
+                    synchronized (mDeviceStateLock) {
+                        speakerphoneClientDied(msg.obj);
+                    }
+                    break;
                 case MSG_TOGGLE_HDMI:
                     synchronized (mDeviceStateLock) {
                         mDeviceInventory.onToggleHdmi();
@@ -1021,7 +1085,7 @@
     private static final int MSG_REPORT_NEW_ROUTES = 13;
     private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
     private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
-    private static final int MSG_I_DISCONNECT_BT_SCO = 16;
+    private static final int MSG_I_SET_MODE_OWNER_PID = 16;
 
     // process active A2DP device change, obj is BtHelper.BluetoothA2dpDeviceInfo
     private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
@@ -1051,6 +1115,8 @@
     private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
     private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
 
+    private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;
+
 
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
@@ -1166,4 +1232,93 @@
                     time);
         }
     }
+
+    private class SpeakerphoneClient implements IBinder.DeathRecipient {
+        private final IBinder mCb;
+        private final int mPid;
+        private final boolean mOn;
+        SpeakerphoneClient(IBinder cb, int pid, boolean on) {
+            mCb = cb;
+            mPid = pid;
+            mOn = on;
+        }
+
+        public boolean registerDeathRecipient() {
+            boolean status = false;
+            try {
+                mCb.linkToDeath(this, 0);
+                status = true;
+            } catch (RemoteException e) {
+                Log.w(TAG, "SpeakerphoneClient could not link to " + mCb + " binder death");
+            }
+            return status;
+        }
+
+        public void unregisterDeathRecipient() {
+            try {
+                mCb.unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                Log.w(TAG, "SpeakerphoneClient could not not unregistered to binder");
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            postSpeakerphoneClientDied(this);
+        }
+
+        IBinder getBinder() {
+            return mCb;
+        }
+
+        int getPid() {
+            return mPid;
+        }
+
+        boolean isOn() {
+            return mOn;
+        }
+    }
+
+    @GuardedBy("mDeviceStateLock")
+    private void speakerphoneClientDied(Object obj) {
+        if (obj == null) {
+            return;
+        }
+        Log.w(TAG, "Speaker client died");
+        if (removeSpeakerphoneClient(((SpeakerphoneClient) obj).getBinder(), false) != null) {
+            updateSpeakerphoneOn("speakerphoneClientDied");
+        }
+    }
+
+    private SpeakerphoneClient removeSpeakerphoneClient(IBinder cb, boolean unregister) {
+        for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+            if (cl.getBinder() == cb) {
+                if (unregister) {
+                    cl.unregisterDeathRecipient();
+                }
+                mSpeakerphoneClients.remove(cl);
+                return cl;
+            }
+        }
+        return null;
+    }
+
+    @GuardedBy("mDeviceStateLock")
+    private boolean addSpeakerphoneClient(IBinder cb, int pid, boolean on) {
+        // always insert new request at first position
+        removeSpeakerphoneClient(cb, true);
+        SpeakerphoneClient client = new SpeakerphoneClient(cb, pid, on);
+        if (client.registerDeathRecipient()) {
+            mSpeakerphoneClients.add(0, client);
+            return true;
+        }
+        return false;
+    }
+
+    // List of clients requesting speakerPhone ON
+    @GuardedBy("mDeviceStateLock")
+    private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =
+            new ArrayList<SpeakerphoneClient>();
+
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8068e37..98d662a 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -52,7 +52,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
@@ -570,6 +569,10 @@
     private int[] mAccessibilityServiceUids;
     private final Object mAccessibilityServiceUidsLock = new Object();
 
+    // Uid of the active input method service to check if caller is the one or not.
+    private int mInputMethodServiceUid = android.os.Process.INVALID_UID;
+    private final Object mInputMethodServiceUidLock = new Object();
+
     private int mEncodedSurroundMode;
     private String mEnabledSurroundFormats;
     private boolean mSurroundModeChanged;
@@ -1078,12 +1081,14 @@
             sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
             sendEnabledSurroundFormats(mContentResolver, true);
             updateAssistantUId(true);
-            updateCurrentImeUid(true);
             AudioSystem.setRttEnabled(mRttEnabled);
         }
         synchronized (mAccessibilityServiceUidsLock) {
             AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
         }
+        synchronized (mInputMethodServiceUidLock) {
+            mAudioSystem.setCurrentImeUid(mInputMethodServiceUid);
+        }
         synchronized (mHdmiClientLock) {
             if (mHdmiManager != null && mHdmiTvClient != null) {
                 setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
@@ -1629,37 +1634,6 @@
         }
     }
 
-    @GuardedBy("mSettingsLock")
-    private void updateCurrentImeUid(boolean forceUpdate) {
-        String imeId = Settings.Secure.getStringForUser(
-                mContentResolver,
-                Settings.Secure.DEFAULT_INPUT_METHOD, UserHandle.USER_CURRENT);
-        if (TextUtils.isEmpty(imeId)) {
-            Log.e(TAG, "updateCurrentImeUid() could not find current IME");
-            return;
-        }
-        ComponentName componentName = ComponentName.unflattenFromString(imeId);
-        if (componentName == null) {
-            Log.e(TAG, "updateCurrentImeUid() got invalid service name for "
-                    + Settings.Secure.DEFAULT_INPUT_METHOD + ": " + imeId);
-            return;
-        }
-        String packageName = componentName.getPackageName();
-        int currentUserId = LocalServices.getService(ActivityManagerInternal.class)
-                .getCurrentUserId();
-        int currentImeUid = LocalServices.getService(PackageManagerInternal.class)
-                .getPackageUidInternal(packageName, 0 /* flags */, currentUserId);
-        if (currentImeUid < 0) {
-            Log.e(TAG, "updateCurrentImeUid() could not find UID for package: " + packageName);
-            return;
-        }
-
-        if (currentImeUid != mCurrentImeUid || forceUpdate) {
-            mAudioSystem.setCurrentImeUid(currentImeUid);
-            mCurrentImeUid = currentImeUid;
-        }
-    }
-
     private void readPersistedSettings() {
         if (!mSystemServer.isPrivileged()) {
             return;
@@ -1707,7 +1681,6 @@
             sendEncodedSurroundMode(cr, "readPersistedSettings");
             sendEnabledSurroundFormats(cr, true);
             updateAssistantUId(true);
-            updateCurrentImeUid(true);
             AudioSystem.setRttEnabled(mRttEnabled);
         }
 
@@ -3594,11 +3567,9 @@
         }
 
         public void binderDied() {
-            int oldModeOwnerPid;
             int newModeOwnerPid = 0;
             synchronized (mDeviceBroker.mSetModeLock) {
                 Log.w(TAG, "setMode() client died");
-                oldModeOwnerPid = getModeOwnerPid();
                 int index = mSetModeDeathHandlers.indexOf(this);
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
@@ -3608,9 +3579,7 @@
             }
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
             // SCO connections not started by the application changing the mode when pid changes
-            if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
-            }
+            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
         }
 
         public int getPid() {
@@ -3662,17 +3631,20 @@
             return;
         }
 
-        int oldModeOwnerPid;
         int newModeOwnerPid;
         synchronized (mDeviceBroker.mSetModeLock) {
             if (mode == AudioSystem.MODE_CURRENT) {
                 mode = mMode;
             }
-            oldModeOwnerPid = getModeOwnerPid();
+            int oldModeOwnerPid = getModeOwnerPid();
             // Do not allow changing mode if a call is active and the requester
-            // does not have permission to modify phone state or is not the mode owner.
-            if (((mMode == AudioSystem.MODE_IN_CALL)
-                    || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
+            // does not have permission to modify phone state or is not the mode owner,
+            // unless returning to NORMAL mode (will not change current mode owner) or
+            // not changing mode in which case the mode owner will reflect the last
+            // requester of current mode
+            if (!((mode == mMode) || (mode == AudioSystem.MODE_NORMAL))
+                    && ((mMode == AudioSystem.MODE_IN_CALL)
+                        || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
                     && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
                 Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
                         + ", uid=" + Binder.getCallingUid()
@@ -3685,9 +3657,7 @@
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
-        if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
-        }
+        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
     }
 
     // setModeInt() returns a valid PID if the audio mode was successfully set to
@@ -3964,32 +3934,18 @@
     }
 
     /** @see AudioManager#setSpeakerphoneOn(boolean) */
-    public void setSpeakerphoneOn(boolean on){
+    public void setSpeakerphoneOn(IBinder cb, boolean on) {
         if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
             return;
         }
 
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_PHONE_STATE)
-                != PackageManager.PERMISSION_GRANTED) {
-            synchronized (mSetModeDeathHandlers) {
-                for (SetModeDeathHandler h : mSetModeDeathHandlers) {
-                    if (h.getMode() == AudioSystem.MODE_IN_CALL) {
-                        Log.w(TAG, "getMode is call, Permission Denial: setSpeakerphoneOn from pid="
-                                + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
-                        return;
-                    }
-                }
-            }
-        }
-
         // for logging only
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
         final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
                 .append(") from u/pid:").append(uid).append("/")
                 .append(pid).toString();
-        final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(on, eventSource);
+        final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
         new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
                 + MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
                 .setUid(uid)
@@ -6217,8 +6173,6 @@
 
             mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.VOICE_INTERACTION_SERVICE), false, this);
-            mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
         }
 
         @Override
@@ -6242,7 +6196,6 @@
                 updateEncodedSurroundOutput();
                 sendEnabledSurroundFormats(mContentResolver, mSurroundModeChanged);
                 updateAssistantUId(false);
-                updateCurrentImeUid(false);
             }
         }
 
@@ -7584,6 +7537,19 @@
                 AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
             }
         }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void setInputMethodServiceUid(int uid) {
+            synchronized (mInputMethodServiceUidLock) {
+                if (mInputMethodServiceUid != uid) {
+                    mAudioSystem.setCurrentImeUid(uid);
+                    mInputMethodServiceUid = uid;
+                }
+            }
+        }
     }
 
     //==========================================================================================
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index b6ffcef..b4c41b2 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -38,6 +38,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.NoSuchElementException;
@@ -131,6 +132,26 @@
         }
     }
 
+    /**
+     * Returns a string representation of the scoAudioState.
+     */
+    public static String scoAudioStateToString(int scoAudioState) {
+        switch (scoAudioState) {
+            case SCO_STATE_INACTIVE:
+                return "SCO_STATE_INACTIVE";
+            case SCO_STATE_ACTIVATE_REQ:
+                return "SCO_STATE_ACTIVATE_REQ";
+            case SCO_STATE_ACTIVE_EXTERNAL:
+                return "SCO_STATE_ACTIVE_EXTERNAL";
+            case SCO_STATE_ACTIVE_INTERNAL:
+                return "SCO_STATE_ACTIVE_INTERNAL";
+            case SCO_STATE_DEACTIVATING:
+                return "SCO_STATE_DEACTIVATING";
+            default:
+                return "SCO_STATE_(" + scoAudioState + ")";
+        }
+    }
+
     //----------------------------------------------------------------------
     /*package*/ static class BluetoothA2dpDeviceInfo {
         private final @NonNull BluetoothDevice mBtDevice;
@@ -1007,4 +1028,20 @@
                 return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
         }
     }
+
+    //------------------------------------------------------------
+    /*package*/ void dump(PrintWriter pw, String prefix) {
+        pw.println("\n" + prefix + "mBluetoothHeadset: " + mBluetoothHeadset);
+        pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
+        pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
+        pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
+        pw.println(prefix + "Sco clients:");
+        mScoClients.forEach((cl) -> {
+            pw.println("  " + prefix + "pid: " + cl.getPid() + " cb: " + cl.getBinder()); });
+
+        pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
+        pw.println(prefix + "mA2dp: " + mA2dp);
+        pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
+    }
+
 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 808f8c21..5a6ab4e5 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -978,6 +978,10 @@
     }
 
     protected void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback) {
+        if (callback == null) {
+            Slog.w(getTag(), "Null LockoutResetCallback");
+            return;
+        }
         mHandler.post(() -> {
            final LockoutResetMonitor monitor = new LockoutResetMonitor(callback);
            if (!mLockoutMonitors.contains(monitor)) {
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 8520f5a..d90f3af 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -330,6 +330,7 @@
         @Override
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback)
                 throws RemoteException {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
             FingerprintService.super.addLockoutResetCallback(callback);
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index c54ebf8..3c05080 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -92,7 +92,7 @@
     private final AppRequestObserver mAppRequestObserver;
     private final SettingsObserver mSettingsObserver;
     private final DisplayObserver mDisplayObserver;
-    private final BrightnessObserver mBrightnessObserver;
+    private BrightnessObserver mBrightnessObserver;
 
     private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
     private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
@@ -460,6 +460,21 @@
         mVotesByDisplay = votesByDisplay;
     }
 
+    @VisibleForTesting
+    void injectBrightnessObserver(BrightnessObserver brightnessObserver) {
+        mBrightnessObserver = brightnessObserver;
+    }
+
+    @VisibleForTesting
+    DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
+            float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
+        synchronized (mLock) {
+            mSettingsObserver.updateRefreshRateSettingLocked(
+                    minRefreshRate, peakRefreshRate, defaultRefreshRate);
+            return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY);
+        }
+    }
+
     /**
      * Listens for changes refresh rate coordination.
      */
@@ -666,14 +681,18 @@
 
     @VisibleForTesting
     static final class Vote {
+        // DEFAULT_FRAME_RATE votes for [0, DEFAULT]. As the lowest priority vote, it's overridden
+        // by all other considerations. It acts to set a default frame rate for a device.
+        public static final int PRIORITY_DEFAULT_REFRESH_RATE = 0;
+
         // LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
         // If the higher voters result is a range, it will fix the rate to a single choice.
         // It's used to avoid rate switch in certain conditions.
-        public static final int PRIORITY_LOW_BRIGHTNESS = 0;
+        public static final int PRIORITY_LOW_BRIGHTNESS = 1;
 
         // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
         // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
-        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 1;
+        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2;
 
         // We split the app request into different priorities in case we can satisfy one desire
         // without the other.
@@ -683,20 +702,20 @@
         // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
         // System also forces some apps like blacklisted app to run at a lower refresh rate.
         // @see android.R.array#config_highRefreshRateBlacklist
-        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 2;
-        public static final int PRIORITY_APP_REQUEST_SIZE = 3;
+        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 3;
+        public static final int PRIORITY_APP_REQUEST_SIZE = 4;
 
         // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
         // of low priority voters. It votes [0, max(PEAK, MIN)]
-        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 4;
+        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 5;
 
         // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
-        public static final int PRIORITY_LOW_POWER_MODE = 5;
+        public static final int PRIORITY_LOW_POWER_MODE = 6;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
         // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
 
-        public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
+        public static final int MIN_PRIORITY = PRIORITY_DEFAULT_REFRESH_RATE;
         public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
 
         // The cutoff for the app request refresh rate range. Votes with priorities lower than this
@@ -740,6 +759,8 @@
 
         public static String priorityToString(int priority) {
             switch (priority) {
+                case PRIORITY_DEFAULT_REFRESH_RATE:
+                    return "PRIORITY_DEFAULT_REFRESH_RATE";
                 case PRIORITY_LOW_BRIGHTNESS:
                     return "PRIORITY_LOW_BRIGHTNESS";
                 case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
@@ -776,12 +797,15 @@
 
         private final Context mContext;
         private float mDefaultPeakRefreshRate;
+        private float mDefaultRefreshRate;
 
         SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
             super(handler);
             mContext = context;
             mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
                     R.integer.config_defaultPeakRefreshRate);
+            mDefaultRefreshRate =
+                    (float) context.getResources().getInteger(R.integer.config_defaultRefreshRate);
         }
 
         public void observe() {
@@ -849,17 +873,48 @@
                     Settings.System.MIN_REFRESH_RATE, 0f);
             float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
                     Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
+            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
+        }
 
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
-                    Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
+        private void updateRefreshRateSettingLocked(
+                float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
+            // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
+            // used to predict if we're going to be doing frequent refresh rate switching, and if
+            // so, enable the brightness observer. The logic here is more complicated and fragile
+            // than necessary, and we should improve it. See b/156304339 for more info.
+            Vote peakVote = peakRefreshRate == 0f
+                    ? null
+                    : Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate));
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, peakVote);
             updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
                     Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
+            Vote defaultVote =
+                    defaultRefreshRate == 0f ? null : Vote.forRefreshRates(0f, defaultRefreshRate);
+            updateVoteLocked(Vote.PRIORITY_DEFAULT_REFRESH_RATE, defaultVote);
 
-            mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
+            float maxRefreshRate;
+            if (peakRefreshRate == 0f && defaultRefreshRate == 0f) {
+                // We require that at least one of the peak or default refresh rate values are
+                // set. The brightness observer requires that we're able to predict whether or not
+                // we're going to do frequent refresh rate switching, and with the way the code is
+                // currently written, we need either a default or peak refresh rate value for that.
+                Slog.e(TAG, "Default and peak refresh rates are both 0. One of them should be set"
+                        + " to a valid value.");
+                maxRefreshRate = minRefreshRate;
+            } else if (peakRefreshRate == 0f) {
+                maxRefreshRate = defaultRefreshRate;
+            } else if (defaultRefreshRate == 0f) {
+                maxRefreshRate = peakRefreshRate;
+            } else {
+                maxRefreshRate = Math.min(defaultRefreshRate, peakRefreshRate);
+            }
+
+            mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
         }
 
         public void dumpLocked(PrintWriter pw) {
             pw.println("  SettingsObserver");
+            pw.println("    mDefaultRefreshRate: " + mDefaultRefreshRate);
             pw.println("    mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
         }
     }
@@ -1014,7 +1069,8 @@
      * {@link R.array#config_brightnessThresholdsOfPeakRefreshRate} and
      * {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
      */
-    private class BrightnessObserver extends ContentObserver {
+    @VisibleForTesting
+    public class BrightnessObserver extends ContentObserver {
         // TODO: brightnessfloat: change this to the float setting
         private final Uri mDisplayBrightnessSetting =
                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 4f5a02a..2a65b33 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -201,7 +201,6 @@
         private SurfaceControl.DisplayConfig[] mDisplayConfigs;
         private Spline mSystemBrightnessToNits;
         private Spline mNitsToHalBrightness;
-        private boolean mHalBrightnessSupport;
 
         private DisplayDeviceConfig mDisplayDeviceConfig;
 
@@ -225,7 +224,6 @@
             }
             mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken);
             mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken);
-            mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken);
             mDisplayDeviceConfig = null;
             // Defer configuration file loading
             BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
@@ -717,11 +715,10 @@
                         Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
                                 + "id=" + physicalDisplayId + ", brightness=" + brightness + ")");
                         try {
-                            // TODO: make it float
                             if (isHalBrightnessRangeSpecified()) {
                                 brightness = displayBrightnessToHalBrightness(
-                                        BrightnessSynchronizer.brightnessFloatToInt(getContext(),
-                                                brightness));
+                                        BrightnessSynchronizer.brightnessFloatToIntRange(
+                                                getContext(), brightness));
                             }
                             if (mBacklight != null) {
                                 mBacklight.setBrightness(brightness);
@@ -744,12 +741,13 @@
                      * Hal brightness space if the HAL brightness space has been provided via
                      * a display device configuration file.
                      */
-                    private float displayBrightnessToHalBrightness(int brightness) {
+                    private float displayBrightnessToHalBrightness(float brightness) {
                         if (!isHalBrightnessRangeSpecified()) {
                             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
                         }
 
-                        if (brightness == 0) {
+                        if (BrightnessSynchronizer.floatEquals(
+                                brightness, PowerManager.BRIGHTNESS_OFF)) {
                             return PowerManager.BRIGHTNESS_OFF_FLOAT;
                         }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 549e336..9de95ab 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2208,8 +2208,12 @@
         public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
             enforceAccessPermission();
             long token = Binder.clearCallingIdentity();
-            HdmiControlService.this.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
-            Binder.restoreCallingIdentity(token);
+            try {
+                HdmiControlService.this.setHdmiCecVolumeControlEnabled(
+                        isHdmiCecVolumeControlEnabled);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 93ef1264..52116a0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -56,6 +56,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
@@ -67,6 +68,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManagerInternal;
 import android.inputmethodservice.InputMethodService;
+import android.media.AudioManagerInternal;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -223,6 +225,8 @@
 
     static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000;
 
+    static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
+
     static final long TIME_TO_RECONNECT = 3 * 1000;
 
     static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -308,6 +312,7 @@
     final SettingsObserver mSettingsObserver;
     final IWindowManager mIWindowManager;
     final WindowManagerInternal mWindowManagerInternal;
+    final PackageManagerInternal mPackageManagerInternal;
     final InputManagerInternal mInputManagerInternal;
     private final DisplayManagerInternal mDisplayManagerInternal;
     final HandlerCaller mCaller;
@@ -320,6 +325,16 @@
     private final UserManager mUserManager;
     private final UserManagerInternal mUserManagerInternal;
 
+    /**
+     * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
+     *
+     * <p>This field is used only within {@link #handleMessage(Message)} hence synchronization is
+     * not necessary.</p>
+     */
+    @Nullable
+    private AudioManagerInternal mAudioManagerInternal = null;
+
+
     // All known input methods.  mMethodMap also serves as the global
     // lock for this class.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
@@ -643,6 +658,11 @@
     IInputMethod mCurMethod;
 
     /**
+     * If not {@link Process#INVALID_UID}, then the UID of {@link #mCurIntent}.
+     */
+    int mCurMethodUid = Process.INVALID_UID;
+
+    /**
      * Time that we last initiated a bind to the input method, to determine
      * if we should try to disconnect and reconnect to it.
      */
@@ -1625,6 +1645,7 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mImeDisplayValidator = displayId -> mWindowManagerInternal.shouldShowIme(displayId);
@@ -2521,11 +2542,26 @@
         return checker.displayCanShowIme(displayId) ? displayId : FALLBACK_DISPLAY_ID;
     }
 
+    @AnyThread
+    private void scheduleNotifyImeUidToAudioService(int uid) {
+        mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
+        mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget();
+    }
+
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
         synchronized (mMethodMap) {
             if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                 mCurMethod = IInputMethod.Stub.asInterface(service);
+                final String curMethodPackage = mCurIntent.getComponent().getPackageName();
+                final int curMethodUid = mPackageManagerInternal.getPackageUidInternal(
+                        curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
+                if (curMethodUid < 0) {
+                    Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
+                    mCurMethodUid = Process.INVALID_UID;
+                } else {
+                    mCurMethodUid = curMethodUid;
+                }
                 if (mCurToken == null) {
                     Slog.w(TAG, "Service connected without a token!");
                     unbindCurrentMethodLocked();
@@ -2535,6 +2571,7 @@
                 // Dispatch display id for InputMethodService to update context display.
                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                         MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken));
+                scheduleNotifyImeUidToAudioService(mCurMethodUid);
                 if (mCurClient != null) {
                     clearClientSessionLocked(mCurClient);
                     requestClientSessionLocked(mCurClient);
@@ -2656,6 +2693,8 @@
             finishSessionLocked(mEnabledSession);
             mEnabledSession = null;
             mCurMethod = null;
+            mCurMethodUid = Process.INVALID_UID;
+            scheduleNotifyImeUidToAudioService(mCurMethodUid);
         }
         if (mStatusBar != null) {
             mStatusBar.setIconVisibility(mSlotIme, false);
@@ -4277,6 +4316,17 @@
                 args.recycle();
                 return true;
             }
+
+            // ---------------------------------------------------------------
+            case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: {
+                if (mAudioManagerInternal == null) {
+                    mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+                }
+                if (mAudioManagerInternal != null) {
+                    mAudioManagerInternal.setInputMethodServiceUid(msg.arg1 /* uid */);
+                }
+                return true;
+            }
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index fd8e03e..6acfd45 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -169,7 +169,9 @@
         for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
             if (device.isConnected()) {
                 BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
-                mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+                if (newBtRoute.connectedProfiles.size() > 0) {
+                    mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+                }
             }
         }
     }
@@ -233,22 +235,32 @@
     private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
         BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
         newBtRoute.btDevice = device;
-        // Current / Max volume will be set when connected.
+        // Current volume will be set when connected.
         // TODO: Is there any BT device which has fixed volume?
         String deviceName = device.getName();
         if (TextUtils.isEmpty(deviceName)) {
             deviceName = mContext.getResources().getText(R.string.unknownName).toString();
         }
+        int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+        newBtRoute.connectedProfiles = new SparseBooleanArray();
+        if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+            newBtRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+        }
+        if (mHearingAidProfile != null
+                && mHearingAidProfile.getConnectedDevices().contains(device)) {
+            newBtRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+            type = MediaRoute2Info.TYPE_HEARING_AID;
+        }
+
         newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), deviceName)
                 .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
                 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
                 .setDescription(mContext.getResources().getText(
                         R.string.bluetooth_a2dp_audio_route_name).toString())
-                .setType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)
+                .setType(type)
                 .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .build();
-        newBtRoute.connectedProfiles = new SparseBooleanArray();
         return newBtRoute;
     }
 
@@ -316,7 +328,6 @@
                     btRoute = createBluetoothRoute(device);
                     mBluetoothRoutes.put(device.getAddress(), btRoute);
                 }
-                btRoute.connectedProfiles.put(profile, true);
                 if (activeDevices.contains(device)) {
                     mSelectedRoute = btRoute;
                     setRouteConnectionState(mSelectedRoute,
@@ -378,18 +389,11 @@
                     BluetoothDevice.ERROR);
             BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
             if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
-                //TODO: The type of the new route is A2DP even when it's HEARING_AID.
-                // We may determine the type of route when create the route.
                 btRoute = createBluetoothRoute(device);
-                if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
-                    btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+                if (btRoute.connectedProfiles.size() > 0) {
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                    notifyBluetoothRoutesUpdated();
                 }
-                if (mHearingAidProfile != null
-                        && mHearingAidProfile.getConnectedDevices().contains(device)) {
-                    btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
-                }
-                mBluetoothRoutes.put(device.getAddress(), btRoute);
-                notifyBluetoothRoutesUpdated();
             } else if (bondState == BluetoothDevice.BOND_NONE
                     && mBluetoothRoutes.remove(device.getAddress()) != null) {
                 notifyBluetoothRoutesUpdated();
@@ -430,9 +434,10 @@
             if (state == BluetoothProfile.STATE_CONNECTED) {
                 if (btRoute == null) {
                     btRoute = createBluetoothRoute(device);
-                    mBluetoothRoutes.put(device.getAddress(), btRoute);
-                    btRoute.connectedProfiles.put(profile, true);
-                    notifyBluetoothRoutesUpdated();
+                    if (btRoute.connectedProfiles.size() > 0) {
+                        mBluetoothRoutes.put(device.getAddress(), btRoute);
+                        notifyBluetoothRoutesUpdated();
+                    }
                 } else {
                     btRoute.connectedProfiles.put(profile, true);
                 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index a435f1e..53205ad 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -60,7 +60,7 @@
     private Connection mActiveConnection;
     private boolean mConnectionReady;
 
-    private RouteDiscoveryPreference mPendingDiscoveryPreference = null;
+    private RouteDiscoveryPreference mLastDiscoveryPreference = null;
 
     MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName,
             int userId) {
@@ -98,11 +98,10 @@
 
     @Override
     public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+        mLastDiscoveryPreference = discoveryPreference;
         if (mConnectionReady) {
             mActiveConnection.updateDiscoveryPreference(discoveryPreference);
             updateBinding();
-        } else {
-            mPendingDiscoveryPreference = discoveryPreference;
         }
     }
 
@@ -277,9 +276,8 @@
     private void onConnectionReady(Connection connection) {
         if (mActiveConnection == connection) {
             mConnectionReady = true;
-            if (mPendingDiscoveryPreference != null) {
-                updateDiscoveryPreference(mPendingDiscoveryPreference);
-                mPendingDiscoveryPreference = null;
+            if (mLastDiscoveryPreference != null) {
+                updateDiscoveryPreference(mLastDiscoveryPreference);
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index bc0e816..84ec440 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1134,8 +1134,19 @@
                 if (cb == null) {
                     throw new IllegalArgumentException("Controller callback cannot be null");
                 }
-                return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag,
-                        sessionInfo).getSessionBinder();
+                MediaSessionRecord session = createSessionInternal(
+                        pid, uid, resolvedUserId, packageName, cb, tag, sessionInfo);
+                if (session == null) {
+                    throw new IllegalStateException("Failed to create a new session record");
+                }
+                ISession sessionBinder = session.getSessionBinder();
+                if (sessionBinder == null) {
+                    throw new IllegalStateException("Invalid session record");
+                }
+                return sessionBinder;
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception in creating a new session", e);
+                throw e;
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bc7bd23..86e8734 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2725,9 +2725,18 @@
         Context appContext = r.getSbn().getPackageContext(getContext());
         Notification.Builder nb =
                 Notification.Builder.recoverBuilder(appContext, r.getNotification());
-        if (nb.getStyle() instanceof Notification.MessagingStyle && r.getShortcutInfo() == null) {
-            mPreferencesHelper.setMessageSent(r.getSbn().getPackageName(), r.getUid());
-            handleSavePolicyFile();
+        if (nb.getStyle() instanceof Notification.MessagingStyle) {
+            if (r.getShortcutInfo() != null) {
+                if (mPreferencesHelper.setValidMessageSent(
+                        r.getSbn().getPackageName(), r.getUid())) {
+                    handleSavePolicyFile();
+                }
+            } else {
+                if (mPreferencesHelper.setInvalidMessageSent(
+                        r.getSbn().getPackageName(), r.getUid())) {
+                    handleSavePolicyFile();
+                }
+            }
         }
     }
 
@@ -3158,9 +3167,22 @@
         }
 
         @Override
-        public boolean hasSentMessage(String pkg, int uid) {
+        public boolean isInInvalidMsgState(String pkg, int uid) {
             checkCallerIsSystem();
-            return mPreferencesHelper.hasSentMessage(pkg, uid);
+            return mPreferencesHelper.isInInvalidMsgState(pkg, uid);
+        }
+
+        @Override
+        public boolean hasUserDemotedInvalidMsgApp(String pkg, int uid) {
+            checkCallerIsSystem();
+            return mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, uid);
+        }
+
+        @Override
+        public void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted) {
+            checkCallerIsSystem();
+            mPreferencesHelper.setInvalidMsgAppDemoted(pkg, uid, isDemoted);
+            handleSavePolicyFile();
         }
 
         @Override
@@ -5698,6 +5720,9 @@
             Slog.w(TAG, "notification " + r.getKey() + " added an invalid shortcut");
         }
         r.setShortcutInfo(info);
+        r.setHasSentValidMsg(mPreferencesHelper.hasSentValidMsg(pkg, notificationUid));
+        r.userDemotedAppFromConvoSpace(
+                mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));
 
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.getSbn().getOverrideGroupKey() != null)) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index a9fa2b1..c107822 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -188,6 +188,8 @@
     private boolean mHasSeenSmartReplies;
     private boolean mFlagBubbleRemoved;
     private boolean mPostSilently;
+    private boolean mHasSentValidMsg;
+    private boolean mAppDemotedFromConvo;
     /**
      * Whether this notification (and its channels) should be considered user locked. Used in
      * conjunction with user sentiment calculation.
@@ -1377,6 +1379,14 @@
         return mShortcutInfo;
     }
 
+    public void setHasSentValidMsg(boolean hasSentValidMsg) {
+        mHasSentValidMsg = hasSentValidMsg;
+    }
+
+    public void userDemotedAppFromConvoSpace(boolean userDemoted) {
+        mAppDemotedFromConvo = userDemoted;
+    }
+
     /**
      * Whether this notification is a conversation notification.
      */
@@ -1397,6 +1407,12 @@
             && mShortcutInfo == null) {
             return false;
         }
+        if (mHasSentValidMsg && mShortcutInfo == null) {
+            return false;
+        }
+        if (mAppDemotedFromConvo) {
+            return false;
+        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index c6ec95a..2b8ee92 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -62,7 +62,7 @@
                 /* android.stats.sysui.NotificationImportance importance_asst = 19 */
                 r.getAssistantImportance(),
                 /* int32 assistant_hash = 20 */ p.getAssistantHash(),
-                /* float assistant_ranking_score = 21 */ 0 // TODO connect up ranking score
+                /* float assistant_ranking_score = 21 */ r.getRankingScore()
         );
     }
 
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index ec0fc4a..9d56d81 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -116,7 +116,9 @@
     private static final String ATT_ENABLED = "enabled";
     private static final String ATT_USER_ALLOWED = "allowed";
     private static final String ATT_HIDE_SILENT = "hide_gentle";
-    private static final String ATT_SENT_MESSAGE = "sent_invalid_msg";
+    private static final String ATT_SENT_INVALID_MESSAGE = "sent_invalid_msg";
+    private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
+    private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
 
     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
@@ -253,8 +255,12 @@
                                     parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
                             r.lockedAppFields = XmlUtils.readIntAttribute(parser,
                                     ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
-                            r.hasSentMessage = XmlUtils.readBooleanAttribute(
-                                    parser, ATT_SENT_MESSAGE, false);
+                            r.hasSentInvalidMessage = XmlUtils.readBooleanAttribute(
+                                    parser, ATT_SENT_INVALID_MESSAGE, false);
+                            r.hasSentValidMessage = XmlUtils.readBooleanAttribute(
+                                    parser, ATT_SENT_VALID_MESSAGE, false);
+                            r.userDemotedMsgApp = XmlUtils.readBooleanAttribute(
+                                    parser, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
 
                             final int innerDepth = parser.getDepth();
                             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -497,7 +503,9 @@
                                 || r.groups.size() > 0
                                 || r.delegate != null
                                 || r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE
-                                || r.hasSentMessage;
+                                || r.hasSentInvalidMessage
+                                || r.userDemotedMsgApp
+                                || r.hasSentValidMessage;
                 if (hasNonDefaultSettings) {
                     out.startTag(null, TAG_PACKAGE);
                     out.attribute(null, ATT_NAME, r.pkg);
@@ -516,7 +524,12 @@
                     out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
                     out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
                             Integer.toString(r.lockedAppFields));
-                    out.attribute(null, ATT_SENT_MESSAGE, Boolean.toString(r.hasSentMessage));
+                    out.attribute(null, ATT_SENT_INVALID_MESSAGE,
+                            Boolean.toString(r.hasSentInvalidMessage));
+                    out.attribute(null, ATT_SENT_VALID_MESSAGE,
+                            Boolean.toString(r.hasSentValidMessage));
+                    out.attribute(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
+                            Boolean.toString(r.userDemotedMsgApp));
 
                     if (!forBackup) {
                         out.attribute(null, ATT_UID, Integer.toString(r.uid));
@@ -635,15 +648,68 @@
         updateConfig();
     }
 
-    public boolean hasSentMessage(String packageName, int uid) {
+    public boolean isInInvalidMsgState(String packageName, int uid) {
         synchronized (mPackagePreferences) {
-            return getOrCreatePackagePreferencesLocked(packageName, uid).hasSentMessage;
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            return r.hasSentInvalidMessage && !r.hasSentValidMessage;
         }
     }
 
-    public void setMessageSent(String packageName, int uid) {
+    public boolean hasUserDemotedInvalidMsgApp(String packageName, int uid) {
         synchronized (mPackagePreferences) {
-            getOrCreatePackagePreferencesLocked(packageName, uid).hasSentMessage = true;
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            return isInInvalidMsgState(packageName, uid) ? r.userDemotedMsgApp : false;
+        }
+    }
+
+    public void setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            r.userDemotedMsgApp = isDemoted;
+        }
+    }
+
+    public boolean setInvalidMessageSent(String packageName, int uid) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            boolean valueChanged = r.hasSentInvalidMessage == false;
+            r.hasSentInvalidMessage = true;
+
+            return valueChanged;
+        }
+    }
+
+    public boolean setValidMessageSent(String packageName, int uid) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            boolean valueChanged = r.hasSentValidMessage == false;
+            r.hasSentValidMessage = true;
+
+            return valueChanged;
+        }
+    }
+
+    @VisibleForTesting
+    boolean hasSentInvalidMsg(String packageName, int uid) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            return r.hasSentInvalidMessage;
+        }
+    }
+
+    @VisibleForTesting
+    boolean hasSentValidMsg(String packageName, int uid) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            return r.hasSentValidMessage;
+        }
+    }
+
+    @VisibleForTesting
+    boolean didUserEverDemoteInvalidMsgApp(String packageName, int uid) {
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
+            return r.userDemotedMsgApp;
         }
     }
 
@@ -1870,6 +1936,9 @@
                     event.writeInt(channel.getImportance());
                     event.writeInt(channel.getUserLockedFields());
                     event.writeBoolean(channel.isDeleted());
+                    event.writeBoolean(channel.getConversationId() != null);
+                    event.writeBoolean(channel.isDemoted());
+                    event.writeBoolean(channel.isImportantConversation());
                     events.add(event.build());
                 }
             }
@@ -2273,7 +2342,11 @@
         boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
         List<String> oemLockedChannels = new ArrayList<>();
         boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
-        boolean hasSentMessage = false;
+
+        boolean hasSentInvalidMessage = false;
+        boolean hasSentValidMessage = false;
+        // notE: only valid while hasSentMessage is false and hasSentInvalidMessage is true
+        boolean userDemotedMsgApp = false;
 
         Delegate delegate = null;
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 70d1ade..f9d805e 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -417,7 +417,7 @@
     public void grantImplicitAccess(int recipientUid, int visibleUid) {
         if (recipientUid != visibleUid
                 && mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) {
-            Slog.wtf(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
+            Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
         }
     }
 
@@ -720,7 +720,7 @@
                 return false;
             }
             if (callingSetting == null) {
-                Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
+                Slog.w(TAG, "No setting found for non system uid " + callingUid);
                 return true;
             }
             final PackageSetting callingPkgSetting;
@@ -760,7 +760,7 @@
             final AndroidPackage targetPkg = targetPkgSetting.pkg;
             if (targetPkg == null) {
                 if (DEBUG_LOGGING) {
-                    Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null");
+                    Slog.w(TAG, "shouldFilterApplication: " + "targetPkg is null");
                 }
                 return true;
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7959461..d73d872 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1566,13 +1566,17 @@
     // Recordkeeping of restore-after-install operations that are currently in flight
     // between the Package Manager and the Backup Manager
     static class PostInstallData {
+        @Nullable
         public final InstallArgs args;
+        @NonNull
         public final PackageInstalledInfo res;
+        @Nullable
         public final Runnable mPostInstallRunnable;
 
-        PostInstallData(InstallArgs _a, PackageInstalledInfo _r, Runnable postInstallRunnable) {
-            args = _a;
-            res = _r;
+        PostInstallData(@Nullable InstallArgs args, @NonNull PackageInstalledInfo res,
+                @Nullable Runnable postInstallRunnable) {
+            this.args = args;
+            this.res = res;
             mPostInstallRunnable = postInstallRunnable;
         }
     }
@@ -1714,7 +1718,7 @@
 
                     if (data != null && data.mPostInstallRunnable != null) {
                         data.mPostInstallRunnable.run();
-                    } else if (data != null) {
+                    } else if (data != null && data.args != null) {
                         InstallArgs args = data.args;
                         PackageInstalledInfo parentRes = data.res;
 
@@ -1732,26 +1736,12 @@
                                 : args.whitelistedRestrictedPermissions;
                         int autoRevokePermissionsMode = args.autoRevokePermissionsMode;
 
-                        // Handle the parent package
                         handlePackagePostInstall(parentRes, grantPermissions,
                                 killApp, virtualPreload, grantedPermissions,
                                 whitelistedRestrictedPermissions, autoRevokePermissionsMode,
                                 didRestore, args.installSource.installerPackageName, args.observer,
                                 args.mDataLoaderType);
 
-                        // Handle the child packages
-                        final int childCount = (parentRes.addedChildPackages != null)
-                                ? parentRes.addedChildPackages.size() : 0;
-                        for (int i = 0; i < childCount; i++) {
-                            PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i);
-                            handlePackagePostInstall(childRes, grantPermissions,
-                                    killApp, virtualPreload, grantedPermissions,
-                                    whitelistedRestrictedPermissions, autoRevokePermissionsMode,
-                                    false /*didRestore*/,
-                                    args.installSource.installerPackageName, args.observer,
-                                    args.mDataLoaderType);
-                        }
-
                         // Log tracing if needed
                         if (args.traceMethod != null) {
                             Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, args.traceMethod,
@@ -2306,27 +2296,8 @@
             // Work that needs to happen on first install within each user
             if (firstUserIds != null && firstUserIds.length > 0) {
                 for (int userId : firstUserIds) {
-                    // If this app is a browser and it's newly-installed for some
-                    // users, clear any default-browser state in those users. The
-                    // app's nature doesn't depend on the user, so we can just check
-                    // its browser nature in any user and generalize.
-                    if (packageIsBrowser(packageName, userId)) {
-                        // If this browser is restored from user's backup, do not clear
-                        // default-browser state for this user
-                        if (pkgSetting.getInstallReason(userId)
-                                != PackageManager.INSTALL_REASON_DEVICE_RESTORE) {
-                            mPermissionManager.setDefaultBrowser(null, true, true, userId);
-                        }
-                    }
-
-                    // We may also need to apply pending (restored) runtime permission grants
-                    // within these users.
-                    mPermissionManager.restoreDelayedRuntimePermissions(packageName,
-                            UserHandle.of(userId));
-
-                    // Persistent preferred activity might have came into effect due to this
-                    // install.
-                    updateDefaultHomeNotLocked(userId);
+                    clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+                            pkgSetting.getInstallReason(userId), userId);
                 }
             }
 
@@ -7210,11 +7181,14 @@
                     sortResult = true;
                 }
             } else {
-                final AndroidPackage pkg = mPackages.get(pkgName);
+                final PackageSetting setting =
+                        getPackageSettingInternal(pkgName, Process.SYSTEM_UID);
                 result = null;
-                if (pkg != null) {
+                if (setting != null && setting.pkg != null
+                        && !shouldFilterApplicationLocked(setting, filterCallingUid, userId)) {
                     result = filterIfNotSystemUser(mComponentResolver.queryActivities(
-                            intent, resolvedType, flags, pkg.getActivities(), userId), userId);
+                            intent, resolvedType, flags, setting.pkg.getActivities(), userId),
+                            userId);
                 }
                 if (result == null || result.size() == 0) {
                     // the caller wants to resolve for a particular package; however, there
@@ -13122,9 +13096,15 @@
                         createPackageInstalledInfo(PackageManager.INSTALL_SUCCEEDED);
                 res.pkg = pkgSetting.pkg;
                 res.newUsers = new int[]{ userId };
-                PostInstallData postInstallData = intentSender == null ? null :
-                        new PostInstallData(null, res, () -> onRestoreComplete(res.returnCode,
-                              mContext, intentSender));
+
+                PostInstallData postInstallData =
+                        new PostInstallData(null, res, () -> {
+                            clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+                                    pkgSetting.getInstallReason(userId), userId);
+                            if (intentSender != null) {
+                                onRestoreComplete(res.returnCode, mContext, intentSender);
+                            }
+                        });
                 restoreAndPostInstall(userId, res, postInstallData);
             }
         } finally {
@@ -13241,7 +13221,9 @@
 
     private void enforceCanSetPackagesSuspendedAsUser(String callingPackage, int callingUid,
             int userId, String callingMethod) {
-        if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) {
+        if (callingUid == Process.ROOT_UID
+                // Need to compare app-id to allow system dialogs access on secondary users
+                || UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
             return;
         }
 
@@ -15801,7 +15783,6 @@
         String returnMsg;
         String installerPackageName;
         PackageRemovedInfo removedInfo;
-        ArrayMap<String, PackageInstalledInfo> addedChildPackages;
         // The set of packages consuming this shared library or null if no consumers exist.
         ArrayList<AndroidPackage> libraryConsumers;
         PackageFreezer freezer;
@@ -15815,37 +15796,21 @@
         public void setError(String msg, PackageParserException e) {
             setReturnCode(e.error);
             setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
-            final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
-            for (int i = 0; i < childCount; i++) {
-                addedChildPackages.valueAt(i).setError(msg, e);
-            }
             Slog.w(TAG, msg, e);
         }
 
         public void setError(String msg, PackageManagerException e) {
             returnCode = e.error;
             setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
-            final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
-            for (int i = 0; i < childCount; i++) {
-                addedChildPackages.valueAt(i).setError(msg, e);
-            }
             Slog.w(TAG, msg, e);
         }
 
         public void setReturnCode(int returnCode) {
             this.returnCode = returnCode;
-            final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
-            for (int i = 0; i < childCount; i++) {
-                addedChildPackages.valueAt(i).returnCode = returnCode;
-            }
         }
 
         private void setReturnMessage(String returnMsg) {
             this.returnMsg = returnMsg;
-            final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0;
-            for (int i = 0; i < childCount; i++) {
-                addedChildPackages.valueAt(i).returnMsg = returnMsg;
-            }
         }
 
         // In some error cases we want to convey more info back to the observer
@@ -17395,7 +17360,6 @@
             int targetParseFlags = parseFlags;
             final PackageSetting ps;
             final PackageSetting disabledPs;
-            final PackageSetting[] childPackages;
             if (replace) {
                 if (parsedPackage.isStaticSharedLibrary()) {
                     // Static libs have a synthetic package name containing the version
@@ -18397,7 +18361,6 @@
             final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
             info.sendPackageRemovedBroadcasts(killApp);
             info.sendSystemPackageUpdatedBroadcasts();
-            info.sendSystemPackageAppearedBroadcasts();
         }
         // Force a gc here.
         Runtime.getRuntime().gc();
@@ -18455,7 +18418,6 @@
         SparseArray<int[]> broadcastWhitelist;
         // Clean up resources deleted packages.
         InstallArgs args = null;
-        ArrayMap<String, PackageInstalledInfo> appearedChildPackages;
 
         PackageRemovedInfo(PackageSender packageSender) {
             this.packageSender = packageSender;
@@ -18471,18 +18433,6 @@
             }
         }
 
-        void sendSystemPackageAppearedBroadcasts() {
-            final int packageCount = (appearedChildPackages != null)
-                    ? appearedChildPackages.size() : 0;
-            for (int i = 0; i < packageCount; i++) {
-                PackageInstalledInfo installedInfo = appearedChildPackages.valueAt(i);
-                packageSender.sendPackageAddedForNewUsers(installedInfo.name,
-                    true /*sendBootCompleted*/, false /*startReceiver*/,
-                        UserHandle.getAppId(installedInfo.uid), installedInfo.newUsers, null,
-                        DataLoaderType.NONE);
-            }
-        }
-
         private void sendSystemPackageUpdatedBroadcastsInternal() {
             Bundle extras = new Bundle(2);
             extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
@@ -19749,6 +19699,30 @@
         }
     }
 
+    private void clearRolesAndRestorePermissionsForNewUserInstall(String packageName,
+            int installReason, @UserIdInt int userId) {
+        // If this app is a browser and it's newly-installed for some
+        // users, clear any default-browser state in those users. The
+        // app's nature doesn't depend on the user, so we can just check
+        // its browser nature in any user and generalize.
+        if (packageIsBrowser(packageName, userId)) {
+            // If this browser is restored from user's backup, do not clear
+            // default-browser state for this user
+            if (installReason != PackageManager.INSTALL_REASON_DEVICE_RESTORE) {
+                mPermissionManager.setDefaultBrowser(null, true, true, userId);
+            }
+        }
+
+        // We may also need to apply pending (restored) runtime permission grants
+        // within these users.
+        mPermissionManager.restoreDelayedRuntimePermissions(packageName,
+                UserHandle.of(userId));
+
+        // Persistent preferred activity might have came into effect due to this
+        // install.
+        updateDefaultHomeNotLocked(userId);
+    }
+
     @Override
     public void resetApplicationPreferences(int userId) {
         mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 7d49f78..163504c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -124,6 +124,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -421,6 +422,10 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+            return;
+        }
+
         mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
     }
 
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3336697..f075790 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -301,10 +301,7 @@
                 }
                 if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
                     String packageName = intent.getData().getSchemeSpecificPart();
-                    if (LOCAL_LOGV) {
-                        Slog.v(TAG, "broadcast=ACTION_PACKAGE_FULLY_REMOVED"
-                                + " pkg=" + packageName);
-                    }
+                    Slog.i(TAG, "broadcast=ACTION_PACKAGE_FULLY_REMOVED pkg=" + packageName);
                     onPackageFullyRemoved(packageName);
                 }
             }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 12309f4..9871623 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -289,6 +289,7 @@
     private StatsPullAtomCallbackImpl mStatsCallbackImpl;
 
     private int mAppOpsSamplingRate = 0;
+    private final ArraySet<Integer> mDangerousAppOpsList = new ArraySet<>();
 
     // Baselines that stores list of NetworkStats right after initializing, with associated
     // information. This is used to calculate difference when pulling
@@ -320,17 +321,10 @@
             try {
                 switch (atomTag) {
                     case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
-                        return pullDataBytesTransfer(atomTag, data, TRANSPORT_WIFI,
-                                /*withFgbg=*/ false);
                     case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
-                        return pullDataBytesTransfer(atomTag, data, TRANSPORT_WIFI,
-                                /*withFgbg=*/ true);
                     case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
-                        return pullDataBytesTransfer(atomTag, data, TRANSPORT_CELLULAR,
-                                /*withFgbg=*/ false);
                     case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
-                        return pullDataBytesTransfer(atomTag, data, TRANSPORT_CELLULAR,
-                                /*withFgbg=*/ true);
+                        return pullDataBytesTransfer(atomTag, data);
                     case FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER:
                         return pullBluetoothBytesTransfer(atomTag, data);
                     case FrameworkStatsLog.KERNEL_WAKELOCK:
@@ -526,6 +520,25 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "failed to initialize healthHalWrapper");
         }
+
+        // Initialize list of AppOps related to DangerousPermissions
+        PackageManager pm = mContext.getPackageManager();
+        for (int op = 0; op < AppOpsManager._NUM_OP; op++) {
+            String perm = AppOpsManager.opToPermission(op);
+            if (perm == null) {
+                continue;
+            } else {
+                PermissionInfo permInfo;
+                try {
+                    permInfo = pm.getPermissionInfo(perm, 0);
+                    if (permInfo.getProtection() == PROTECTION_DANGEROUS) {
+                        mDangerousAppOpsList.add(op);
+                    }
+                } catch (PackageManager.NameNotFoundException exception) {
+                    continue;
+                }
+            }
+        }
     }
 
     void registerEventListeners() {
@@ -619,10 +632,14 @@
             Slog.d(TAG, "Registering NetworkStats pullers with statsd");
         }
         // Initialize NetworkStats baselines.
-        mNetworkStatsBaselines.addAll(collectWifiBytesTransferSnapshot(/*withFgbg=*/ false));
-        mNetworkStatsBaselines.addAll(collectWifiBytesTransferSnapshot(/*withFgbg=*/ true));
-        mNetworkStatsBaselines.addAll(collectMobileBytesTransferSnapshot(/*withFgbg=*/ false));
-        mNetworkStatsBaselines.addAll(collectMobileBytesTransferSnapshot(/*withFgbg=*/ true));
+        mNetworkStatsBaselines.addAll(
+                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
+        mNetworkStatsBaselines.addAll(
+                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
+        mNetworkStatsBaselines.addAll(
+                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
+        mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+                FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
 
         registerWifiBytesTransfer();
         registerWifiBytesTransferBackground();
@@ -780,36 +797,42 @@
     }
 
     @NonNull
-    private List<NetworkStatsExt> collectWifiBytesTransferSnapshot(boolean withFgbg) {
+    private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) {
+        switch(atomTag) {
+            case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
+                return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/false);
+            case  FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
+                return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/true);
+            case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
+                return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/false);
+            case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
+                return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/true);
+            default:
+                throw new IllegalArgumentException("Unknown atomTag " + atomTag);
+        }
+    }
+
+    // Get a snapshot of Uid NetworkStats. The snapshot contains NetworkStats with its associated
+    // information, and wrapped by a list since multiple NetworkStatsExt objects might be collected.
+    @NonNull
+    private List<NetworkStatsExt> collectUidNetworkStatsSnapshot(int transport, boolean withFgbg) {
         final List<NetworkStatsExt> ret = new ArrayList<>();
-        final NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard();
+        final NetworkTemplate template = (transport == TRANSPORT_CELLULAR
+                ? NetworkTemplate.buildTemplateMobileWithRatType(
+                        /*subscriptionId=*/null, NETWORK_TYPE_ALL)
+                : NetworkTemplate.buildTemplateWifiWildcard());
+
         final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg);
         if (stats != null) {
-            ret.add(new NetworkStatsExt(stats, TRANSPORT_WIFI, withFgbg));
+            ret.add(new NetworkStatsExt(stats, transport, withFgbg));
         }
         return ret;
     }
 
-    // Get a snapshot of mobile data usage. The snapshot contains NetworkStats with its associated
-    // information, and wrapped by a list since multiple NetworkStatsExt objects might be collected.
-    // TODO: Slice NetworkStats to multiple objects by RAT type or subscription.
-    @NonNull
-    private List<NetworkStatsExt> collectMobileBytesTransferSnapshot(boolean withFgbg) {
-        final List<NetworkStatsExt> ret = new ArrayList<>();
-        final NetworkTemplate template =
-                NetworkTemplate.buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL);
-        final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg);
-        if (stats != null) {
-            ret.add(new NetworkStatsExt(stats, TRANSPORT_CELLULAR, withFgbg));
-        }
-        return ret;
-    }
 
     private int pullDataBytesTransfer(
-            int atomTag, @NonNull List<StatsEvent> pulledData, int transport, boolean withFgbg) {
-        final List<NetworkStatsExt> current =
-                (transport == TRANSPORT_CELLULAR ? collectMobileBytesTransferSnapshot(withFgbg)
-                        : collectWifiBytesTransferSnapshot(withFgbg));
+            int atomTag, @NonNull List<StatsEvent> pulledData) {
+        final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtom(atomTag);
 
         if (current == null) {
             Slog.e(TAG, "current snapshot is null for " + atomTag + ", return.");
@@ -824,7 +847,7 @@
             // skip reporting anything since the snapshot is invalid.
             if (baseline == null) {
                 Slog.e(TAG, "baseline is null for " + atomTag + ", transport="
-                        + item.transport + " , withFgbg=" + withFgbg + ", return.");
+                        + item.transport + " , withFgbg=" + item.withFgbg + ", return.");
                 return StatsManager.PULL_SKIP;
             }
             final NetworkStatsExt diff = new NetworkStatsExt(item.stats.subtract(
@@ -3113,22 +3136,8 @@
         e.writeLong(op.getBackgroundRejectCount(OP_FLAGS_PULLED));
         e.writeLong(op.getForegroundAccessDuration(OP_FLAGS_PULLED));
         e.writeLong(op.getBackgroundAccessDuration(OP_FLAGS_PULLED));
+        e.writeBoolean(mDangerousAppOpsList.contains(op.getOpCode()));
 
-        String perm = AppOpsManager.opToPermission(op.getOpCode());
-        if (perm == null) {
-            e.writeBoolean(false);
-        } else {
-            PermissionInfo permInfo;
-            try {
-                permInfo = mContext.getPackageManager().getPermissionInfo(
-                        perm,
-                        0);
-                e.writeBoolean(
-                        permInfo.getProtection() == PROTECTION_DANGEROUS);
-            } catch (PackageManager.NameNotFoundException exception) {
-                e.writeBoolean(false);
-            }
-        }
         if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) {
             e.writeInt(mAppOpsSamplingRate);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e675afc..0598680 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1940,6 +1940,7 @@
             mStartingData = null;
             startingSurface = null;
             startingWindow = null;
+            startingDisplayed = false;
             if (surface == null) {
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                         "startingWindow was set but startingSurface==null, couldn't "
@@ -5456,6 +5457,7 @@
         if (mLastTransactionSequence != mWmService.mTransactionSequence) {
             mLastTransactionSequence = mWmService.mTransactionSequence;
             mNumDrawnWindows = 0;
+            startingDisplayed = false;
 
             // There is the main base application window, even if it is exiting, wait for it
             mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6682,6 +6684,13 @@
     }
 
     @Override
+    void getAnimationPosition(Point outPosition) {
+        // Always animate from zero because if the activity doesn't fill the task, the letterbox
+        // will fill the remaining area that should be included in the animation.
+        outPosition.set(0, 0);
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         if (mCompatDisplayInsets != null) {
             Configuration overrideConfig = getRequestedOverrideConfiguration();
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index cb2d98c..2f868d9 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -382,6 +382,7 @@
             return mBehindFullscreenActivity;
         }
 
+        /** Returns {@code true} to stop the outer loop and indicate the result is computed. */
         private boolean processActivity(ActivityRecord r, ActivityRecord topActivity) {
             if (mAboveTop) {
                 if (r == topActivity) {
@@ -397,7 +398,10 @@
             }
 
             if (mHandlingOccluded) {
-                mHandleBehindFullscreenActivity.accept(r);
+                // Iterating through all occluded activities.
+                if (mBehindFullscreenActivity) {
+                    mHandleBehindFullscreenActivity.accept(r);
+                }
             } else if (r == mToCheck) {
                 return true;
             } else if (mBehindFullscreenActivity) {
@@ -1307,8 +1311,17 @@
     /**
      * Make sure that all activities that need to be visible in the stack (that is, they
      * currently can be seen by the user) actually are and update their configuration.
+     * @param starting The top most activity in the task.
+     *                 The activity is either starting or resuming.
+     *                 Caller should ensure starting activity is visible.
+     * @param preserveWindows Flag indicating whether windows should be preserved when updating
+     *                        configuration in {@link mEnsureActivitiesVisibleHelper}.
+     * @param configChanges Parts of the configuration that changed for this activity for evaluating
+     *                      if the screen should be frozen as part of
+     *                      {@link mEnsureActivitiesVisibleHelper}.
+     *
      */
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
             boolean preserveWindows) {
         ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
     }
@@ -1317,9 +1330,19 @@
      * Ensure visibility with an option to also update the configuration of visible activities.
      * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
      * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @param starting The top most activity in the task.
+     *                 The activity is either starting or resuming.
+     *                 Caller should ensure starting activity is visible.
+     * @param notifyClients Flag indicating whether the visibility updates should be sent to the
+     *                      clients in {@link mEnsureActivitiesVisibleHelper}.
+     * @param preserveWindows Flag indicating whether windows should be preserved when updating
+     *                        configuration in {@link mEnsureActivitiesVisibleHelper}.
+     * @param configChanges Parts of the configuration that changed for this activity for evaluating
+     *                      if the screen should be frozen as part of
+     *                      {@link mEnsureActivitiesVisibleHelper}.
      */
     // TODO: Should be re-worked based on the fact that each task as a stack in most cases.
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
         mTopActivityOccludesKeyguard = false;
         mTopDismissingKeyguardActivity = null;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index dfa3fe0..c28d47c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
 
@@ -193,9 +192,7 @@
         final ActivityStack homeStack;
         try {
             // Make sure home stack exists on display area.
-            // TODO(b/153624902): Replace with TaskDisplayArea#getOrCreateRootHomeTask()
-            homeStack = taskDisplayArea.getOrCreateStack(WINDOWING_MODE_UNDEFINED,
-                    ACTIVITY_TYPE_HOME, ON_TOP);
+            homeStack = taskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
         } finally {
             mSupervisor.endDeferResume();
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 79e8ee3..bcdd6e3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1539,7 +1539,10 @@
      *
      * Note: This method should only be called from {@link #startActivityUnchecked}.
      */
-    private int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
+
+    // TODO(b/152429287): Make it easier to exercise code paths through startActivityInner
+    @VisibleForTesting
+    int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             int startFlags, boolean doResume, ActivityOptions options, Task inTask,
             boolean restrictedBgActivity) {
@@ -1660,7 +1663,10 @@
                 // Also, we don't want to resume activities in a task that currently has an overlay
                 // as the starting activity just needs to be in the visible paused state until the
                 // over is removed.
-                mTargetStack.ensureActivitiesVisible(mStartActivity, 0, !PRESERVE_WINDOWS);
+                // Passing {@code null} as the start parameter ensures all activities are made
+                // visible.
+                mTargetStack.ensureActivitiesVisible(null /* starting */,
+                        0 /* configChanges */, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
                 mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 78e4237..fdbb2b2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3319,7 +3319,7 @@
     }
 
     @Override
-    public void resizeTask(int taskId, Rect bounds, int resizeMode) {
+    public boolean resizeTask(int taskId, Rect bounds, int resizeMode) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeTask()");
         long ident = Binder.clearCallingIdentity();
         try {
@@ -3328,10 +3328,11 @@
                         MATCH_TASK_IN_STACKS_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
-                    return;
+                    return false;
                 }
                 if (!task.getWindowConfiguration().canResizeTask()) {
-                    throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
+                    Slog.w(TAG, "resizeTask not allowed on task=" + task);
+                    return false;
                 }
 
                 // Reparent the task to the right stack if necessary
@@ -3339,7 +3340,7 @@
 
                 // After reparenting (which only resizes the task to the stack bounds), resize the
                 // task to the actual bounds provided
-                task.resize(bounds, resizeMode, preserveWindow);
+                return task.resize(bounds, resizeMode, preserveWindow);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 345cfb0..a45a15b 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -66,6 +66,7 @@
     final int mFeatureId;
     private final DisplayAreaOrganizerController mOrganizerController;
     IDisplayAreaOrganizer mOrganizer;
+    private final Configuration mTmpConfiguration = new Configuration();
 
     DisplayArea(WindowManagerService wms, Type type, String name) {
         this(wms, type, name, FEATURE_UNDEFINED);
@@ -162,8 +163,10 @@
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
+        mTmpConfiguration.setTo(getConfiguration());
         super.onConfigurationChanged(newParentConfig);
-        if (mOrganizer != null) {
+
+        if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) {
             mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3acb127..85c439c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3317,7 +3317,7 @@
                     mInputMethodWindow.getDisplayId());
         }
         mInsetsStateController.getSourceProvider(ITYPE_IME).setWindow(win,
-                null /* frameProvider */, null /* imeFrameProvider */);
+                mDisplayPolicy.getImeSourceFrameProvider(), null /* imeFrameProvider */);
         computeImeTarget(true /* updateImeTarget */);
         updateImeControlTarget();
     }
@@ -4700,9 +4700,11 @@
     boolean supportsSystemDecorations() {
         return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
                 || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
-                || (mWmService.mForceDesktopModeOnExternalDisplays && !isUntrustedVirtualDisplay()))
+                || mWmService.mForceDesktopModeOnExternalDisplays)
                 // VR virtual display will be used to run and render 2D app within a VR experience.
-                && mDisplayId != mWmService.mVr2dDisplayId;
+                && mDisplayId != mWmService.mVr2dDisplayId
+                // Do not show system decorations on untrusted virtual display.
+                && !isUntrustedVirtualDisplay();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2e18fbf..2f18a0d 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1106,6 +1106,24 @@
         }
     }
 
+    TriConsumer<DisplayFrames, WindowState, Rect> getImeSourceFrameProvider() {
+        return (displayFrames, windowState, inOutFrame) -> {
+            if (mNavigationBar != null && navigationBarPosition(displayFrames.mDisplayWidth,
+                    displayFrames.mDisplayHeight,
+                    displayFrames.mRotation) == NAV_BAR_BOTTOM) {
+                // In gesture navigation, nav bar frame is larger than frame to calculate insets.
+                // IME should not provide frame which is smaller than the nav bar frame. Otherwise,
+                // nav bar might be overlapped with the content of the client when IME is shown.
+                sTmpRect.set(inOutFrame);
+                sTmpRect.intersectUnchecked(mNavigationBar.getFrameLw());
+                inOutFrame.inset(windowState.getGivenContentInsetsLw());
+                inOutFrame.union(sTmpRect);
+            } else {
+                inOutFrame.inset(windowState.getGivenContentInsetsLw());
+            }
+        };
+    }
+
     private static void enforceSingleInsetsTypeCorrespondingToWindowType(int[] insetsTypes) {
         int count = 0;
         for (int insetsType : insetsTypes) {
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c92de2b..c4e03f5 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.ActivityStack.TAG_VISIBILITY;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
 
+import android.annotation.Nullable;
 import android.util.Slog;
 
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -42,6 +43,16 @@
         mContiner = container;
     }
 
+    /**
+     * Update all attributes except {@link mContiner} to use in subsequent calculations.
+     *
+     * @param starting The activity that is being started
+     * @param configChanges Parts of the configuration that changed for this activity for evaluating
+     *                      if the screen should be frozen.
+     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
+     * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
+     *                      be sent to the clients.
+     */
     void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
             boolean notifyClients) {
         mStarting = starting;
@@ -60,8 +71,17 @@
      * Ensure visibility with an option to also update the configuration of visible activities.
      * @see ActivityStack#ensureActivitiesVisible(ActivityRecord, int, boolean)
      * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @param starting The top most activity in the task.
+     *                 The activity is either starting or resuming.
+     *                 Caller should ensure starting activity is visible.
+     *
+     * @param configChanges Parts of the configuration that changed for this activity for evaluating
+     *                      if the screen should be frozen.
+     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
+     * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
+     *                      be sent to the clients.
      */
-    void process(ActivityRecord starting, int configChanges, boolean preserveWindows,
+    void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
             boolean notifyClients) {
         reset(starting, configChanges, preserveWindows, notifyClients);
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 1b1898b..efcd61d 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -469,8 +469,12 @@
         public void accept(WindowState w) {
             final InputChannel inputChannel = w.mInputChannel;
             final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
+            final RecentsAnimationController recentsAnimationController =
+                    mService.getRecentsAnimationController();
+            final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
+                    && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
             if (inputChannel == null || inputWindowHandle == null || w.mRemoved
-                    || w.cantReceiveTouchInput()) {
+                    || (w.cantReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
                 if (w.mWinAnimator.hasSurface()) {
                     mInputTransaction.setInputWindowInfo(
                         w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
@@ -486,22 +490,16 @@
             final boolean hasFocus = w.isFocused();
             final boolean isVisible = w.isVisibleLw();
 
-            if (mAddRecentsAnimationInputConsumerHandle) {
-                final RecentsAnimationController recentsAnimationController =
-                        mService.getRecentsAnimationController();
-                if (recentsAnimationController != null
-                        && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord)) {
-                    if (recentsAnimationController.updateInputConsumerForApp(
-                            mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
-                        mRecentsAnimationInputConsumer.show(mInputTransaction, w);
-                        mAddRecentsAnimationInputConsumerHandle = false;
-                    }
+            if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
+                if (recentsAnimationController.updateInputConsumerForApp(
+                        mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
+                    mRecentsAnimationInputConsumer.show(mInputTransaction, w);
+                    mAddRecentsAnimationInputConsumerHandle = false;
                 }
             }
 
             if (w.inPinnedWindowingMode()) {
                 if (mAddPipInputConsumerHandle) {
-
                     final Task rootTask = w.getTask().getRootTask();
                     mPipInputConsumer.mWindowHandle.replaceTouchableRegionWithCrop(
                             rootTask.getSurfaceControl());
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index d6ddcd0..cbc1bdf 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -799,7 +799,8 @@
         // Only apply the input consumer if it is enabled, it is not the target (home/recents)
         // being revealed with the transition, and we are actively animating the app as a part of
         // the animation
-        return mInputConsumerEnabled && !isTargetApp(activity) && isAnimatingApp(activity);
+        return mInputConsumerEnabled && activity != null
+                && !isTargetApp(activity) && isAnimatingApp(activity);
     }
 
     boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a2979e6..0ecde72 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1369,7 +1369,7 @@
         calculateDefaultMinimalSizeOfResizeableTasks();
 
         final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
-        defaultTaskDisplayArea.getOrCreateRootHomeTask();
+        defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
         positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
                 false /* includingParents */);
     }
@@ -2176,6 +2176,10 @@
                 // move the PIP activity into the task.
                 stack = taskDisplayArea.createStack(WINDOWING_MODE_UNDEFINED, r.getActivityType(),
                         ON_TOP, r.info, r.intent, false /* createdByOrganizer */);
+                // It's possible the task entering PIP is in freeform, so save the last
+                // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
+                // to its previous freeform bounds.
+                stack.setLastNonFullscreenBounds(task.mLastNonFullscreenBounds);
 
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 37a4c1f..6ce36f1 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1461,16 +1461,23 @@
         return mChildren.get(index);
     }
 
+    @Nullable
+    ActivityStack getOrCreateRootHomeTask() {
+        return getOrCreateRootHomeTask(false /* onTop */);
+    }
+
     /**
      * Returns the existing home stack or creates and returns a new one if it should exist for the
      * display.
+     * @param onTop Only be used when there is no existing home stack. If true the home stack will
+     *              be created at the top of the display, else at the bottom.
      */
     @Nullable
-    ActivityStack getOrCreateRootHomeTask() {
+    ActivityStack getOrCreateRootHomeTask(boolean onTop) {
         ActivityStack homeTask = getRootHomeTask();
         if (homeTask == null && mDisplayContent.supportsSystemDecorations()
                 && !mDisplayContent.isUntrustedVirtualDisplay()) {
-            homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, false /* onTop */);
+            homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, onTop);
         }
         return homeTask;
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 45023ac..c6e1c95 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -28,12 +28,14 @@
 import android.graphics.Bitmap.Config;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserManagerInternal;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
 
 import java.io.File;
@@ -72,6 +74,7 @@
     private final float mLowResScaleFactor;
     private boolean mEnableLowResSnapshots;
     private final boolean mUse16BitFormat;
+    private final UserManagerInternal mUserManagerInternal;
 
     /**
      * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
@@ -82,6 +85,8 @@
 
     TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
         mDirectoryResolver = resolver;
+        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+
         final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
                 com.android.internal.R.dimen.config_highResTaskSnapshotScale);
         final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
@@ -191,7 +196,7 @@
                     return;
                 }
             }
-            SystemClock.sleep(100);
+            SystemClock.sleep(DELAY_MS);
         }
     }
 
@@ -233,7 +238,7 @@
 
     private boolean createDirectory(int userId) {
         final File dir = getDirectory(userId);
-        return dir.exists() || dir.mkdirs();
+        return dir.exists() || dir.mkdir();
     }
 
     private void deleteSnapshot(int taskId, int userId) {
@@ -258,18 +263,26 @@
             android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
             while (true) {
                 WriteQueueItem next;
+                boolean isReadyToWrite = false;
                 synchronized (mLock) {
                     if (mPaused) {
                         next = null;
                     } else {
                         next = mWriteQueue.poll();
                         if (next != null) {
-                            next.onDequeuedLocked();
+                            if (next.isReady()) {
+                                isReadyToWrite = true;
+                                next.onDequeuedLocked();
+                            } else {
+                                mWriteQueue.addLast(next);
+                            }
                         }
                     }
                 }
                 if (next != null) {
-                    next.write();
+                    if (isReadyToWrite) {
+                        next.write();
+                    }
                     SystemClock.sleep(DELAY_MS);
                 }
                 synchronized (mLock) {
@@ -289,6 +302,13 @@
     };
 
     private abstract class WriteQueueItem {
+        /**
+         * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
+         */
+        boolean isReady() {
+            return true;
+        }
+
         abstract void write();
 
         /**
@@ -328,6 +348,11 @@
         }
 
         @Override
+        boolean isReady() {
+            return mUserManagerInternal.isUserUnlocked(mUserId);
+        }
+
+        @Override
         void write() {
             if (!createDirectory(mUserId)) {
                 Slog.e(TAG, "Unable to create snapshot directory for user dir="
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index b9b6c08..d1cb210 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -238,23 +238,12 @@
         }
     }
 
-    private final boolean isWallpaperVisible(WindowState wallpaperTarget) {
-        final RecentsAnimationController recentsAnimationController =
-                mService.getRecentsAnimationController();
-        boolean isAnimatingWithRecentsComponent = recentsAnimationController != null
-                && recentsAnimationController.isWallpaperVisible(wallpaperTarget);
-        if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
-                + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
-                + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mActivityRecord != null)
-                ? wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS) : null)
-                + " prev=" + mPrevWallpaperTarget
-                + " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent);
-        return (wallpaperTarget != null
-                && (!wallpaperTarget.mObscured
-                        || isAnimatingWithRecentsComponent
-                        || (wallpaperTarget.mActivityRecord != null
-                        && wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS))))
-                || mPrevWallpaperTarget != null;
+    private boolean isWallpaperVisible(WindowState wallpaperTarget) {
+        if (DEBUG_WALLPAPER) {
+            Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev="
+                    + mPrevWallpaperTarget);
+        }
+        return wallpaperTarget != null || mPrevWallpaperTarget != null;
     }
 
     boolean isWallpaperTargetAnimating() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5d7ec12..7bfddd7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2120,6 +2120,11 @@
         return getBounds();
     }
 
+    /** Gets the position relative to parent for animation. */
+    void getAnimationPosition(Point outPosition) {
+        getRelativePosition(outPosition);
+    }
+
     /**
      * Applies the app transition animation according the given the layout properties in the
      * window hierarchy.
@@ -2178,9 +2183,9 @@
 
         // Separate position and size for use in animators.
         mTmpRect.set(getAnimationBounds(appStackClipMode));
-        if (sHierarchicalAnimations) {
-            getRelativePosition(mTmpPoint);
-        } else {
+        getAnimationPosition(mTmpPoint);
+        if (!sHierarchicalAnimations) {
+            // Non-hierarchical animation uses position in global coordinates.
             mTmpPoint.set(mTmpRect.left, mTmpRect.top);
         }
         mTmpRect.offsetTo(0, 0);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a948ece..e925ce5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2191,9 +2191,9 @@
 
                 if (wasVisible) {
                     final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
-                    final int flags = startingWindow ? 0 /* self */ : PARENTS;
+
                     // Try starting an animation.
-                    if (mWinAnimator.applyAnimationLocked(transit, false, flags)) {
+                    if (mWinAnimator.applyAnimationLocked(transit, false)) {
                         mAnimatingExit = true;
 
                         // mAnimatingExit affects canAffectSystemUiFlags(). Run layout such that
@@ -2205,9 +2205,7 @@
                         mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
                     }
                 }
-                final boolean isAnimating = startingWindow
-                        ? isAnimating(0)
-                        : isAnimating(TRANSITION | PARENTS)
+                final boolean isAnimating = isAnimating(TRANSITION | PARENTS)
                         && (mActivityRecord == null || !mActivityRecord.isWaitingForTransitionStart());
                 final boolean lastWindowIsStartingWindow = startingWindow && mActivityRecord != null
                         && mActivityRecord.isLastWindow(this);
@@ -2229,9 +2227,6 @@
                 }
             }
 
-            if (startingWindow && mActivityRecord != null) {
-                mActivityRecord.startingDisplayed = false;
-            }
             removeImmediately();
             // Removing a visible window will effect the computed orientation
             // So just update orientation if needed.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e70f3e4..c570cf1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1400,25 +1400,9 @@
      *      the switch statement below.
      * @param isEntrance The animation type the last time this was called. Used to keep from
      *      loading the same animation twice.
-     * @return {@code true} if an animation has been loaded, includes the parents.
-     *
+     * @return true if an animation has been loaded.
      */
     boolean applyAnimationLocked(int transit, boolean isEntrance) {
-        return applyAnimationLocked(transit, isEntrance, PARENTS);
-    }
-
-    /**
-     * Choose the correct animation and set it to the passed WindowState.
-     * @param transit If AppTransition.TRANSIT_PREVIEW_DONE and the app window has been drawn
-     *      then the animation will be app_starting_exit. Any other value loads the animation from
-     *      the switch statement below.
-     * @param isEntrance The animation type the last time this was called. Used to keep from
-     *      loading the same animation twice.
-     * @param flags The combination of bitmask flags to specify targets and condition for
-     *      checking animating status. See {@link WindowContainer.AnimationFlags}.
-     * @return {@code true} if an animation has been loaded.
-     */
-    boolean applyAnimationLocked(int transit, boolean isEntrance, int flags) {
         if (mWin.isAnimating() && mAnimationIsEntrance == isEntrance) {
             // If we are trying to apply an animation, but already running
             // an animation of the same type, then just leave that one alone.
@@ -1488,7 +1472,7 @@
             mWin.getDisplayContent().adjustForImeIfNeeded();
         }
 
-        return mWin.isAnimating(flags);
+        return mWin.isAnimating(PARENTS);
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 60d59b2..18c25c1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -247,6 +247,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -11965,10 +11966,21 @@
     }
 
     private void showLocationSettingsChangedNotification(UserHandle user) {
+        Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Fill the component explicitly to prevent the PendingIntent from being intercepted
+        // and fired with crafted target. b/155183624
+        ActivityInfo targetInfo = intent.resolveActivityInfo(
+                mInjector.getPackageManager(user.getIdentifier()),
+                PackageManager.MATCH_SYSTEM_ONLY);
+        if (targetInfo != null) {
+            intent.setComponent(targetInfo.getComponentName());
+        } else {
+            Slog.wtf(LOG_TAG, "Failed to resolve intent for location settings");
+        }
+
         PendingIntent locationSettingsIntent = mInjector.pendingIntentGetActivityAsUser(mContext, 0,
-                new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT,
-                null, user);
+                intent, PendingIntent.FLAG_UPDATE_CURRENT, null, user);
         Notification notification = new Notification.Builder(mContext,
                 SystemNotificationChannels.DEVICE_ADMIN)
                 .setSmallIcon(R.drawable.ic_info_outline)
@@ -15994,31 +16006,27 @@
     private @PersonalAppsSuspensionReason int updatePersonalAppsSuspension(
             int profileUserId, boolean unlocked) {
         final boolean suspendedExplicitly;
-        final int deadlineState;
-        final String poPackage;
+        final boolean suspendedByTimeout;
         synchronized (getLockObject()) {
             final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
             if (profileOwner != null) {
-                deadlineState =
+                final int deadlineState =
                         updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
                 suspendedExplicitly = profileOwner.mSuspendPersonalApps;
-                poPackage = profileOwner.info.getPackageName();
+                suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED;
+                Slog.d(LOG_TAG, String.format(
+                        "Personal apps suspended explicitly: %b, deadline state: %d",
+                        suspendedExplicitly, deadlineState));
+                final int notificationState =
+                        unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState;
+                updateProfileOffDeadlineNotificationLocked(
+                        profileUserId, profileOwner, notificationState);
             } else {
-                poPackage = null;
                 suspendedExplicitly = false;
-                deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+                suspendedByTimeout = false;
             }
         }
 
-        Slog.d(LOG_TAG, String.format("Personal apps suspended explicitly: %b, deadline state: %d",
-                suspendedExplicitly, deadlineState));
-
-        if (poPackage != null) {
-            final int notificationState = unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState;
-            updateProfileOffDeadlineNotification(profileUserId, poPackage, notificationState);
-        }
-
-        final boolean suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED;
         final int parentUserId = getProfileParentId(profileUserId);
         suspendPersonalAppsInternal(parentUserId, suspendedExplicitly || suspendedByTimeout);
 
@@ -16130,9 +16138,9 @@
         }
     }
 
-    private void updateProfileOffDeadlineNotification(
-            int profileUserId, String profileOwnerPackage, int notificationState) {
-
+    @GuardedBy("getLockObject()")
+    private void updateProfileOffDeadlineNotificationLocked(
+            int profileUserId, ActiveAdmin profileOwner, int notificationState) {
         if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) {
             mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED);
             return;
@@ -16150,11 +16158,23 @@
         final Notification.Action turnProfileOnButton =
                 new Notification.Action.Builder(null /* icon */, buttonText, pendingIntent).build();
 
-        final String text = mContext.getString(
-                notificationState == PROFILE_OFF_DEADLINE_WARNING
-                ? R.string.personal_apps_suspension_tomorrow_text
-                : R.string.personal_apps_suspension_text);
-        final boolean ongoing = notificationState == PROFILE_OFF_DEADLINE_REACHED;
+        final String text;
+        final boolean ongoing;
+        if (notificationState == PROFILE_OFF_DEADLINE_WARNING) {
+            // Round to the closest integer number of days.
+            final int maxDays = (int)
+                    ((profileOwner.mProfileMaximumTimeOffMillis + MS_PER_DAY / 2) / MS_PER_DAY);
+            final String date = DateUtils.formatDateTime(
+                    mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_DATE);
+            final String time = DateUtils.formatDateTime(
+                    mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_TIME);
+            text = mContext.getString(
+                    R.string.personal_apps_suspension_soon_text, date, time, maxDays);
+            ongoing = false;
+        } else {
+            text = mContext.getString(R.string.personal_apps_suspension_text);
+            ongoing = true;
+        }
         final int color = mContext.getColor(R.color.personal_apps_suspension_notification_color);
         final Bundle extras = new Bundle();
         // TODO: Create a separate string for this.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
index 7cfbcc8..1630f27 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
@@ -22,9 +22,12 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
+import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -38,6 +41,7 @@
  */
 class RemoteBugreportUtils {
 
+    private static final String TAG = "RemoteBugreportUtils";
     static final int NOTIFICATION_ID = SystemMessage.NOTE_REMOTE_BUGREPORT;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -60,6 +64,17 @@
         Intent dialogIntent = new Intent(Settings.ACTION_SHOW_REMOTE_BUGREPORT_DIALOG);
         dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         dialogIntent.putExtra(DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE, type);
+
+        // Fill the component explicitly to prevent the PendingIntent from being intercepted
+        // and fired with crafted target. b/155183624
+        ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
+                context.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
+        if (targetInfo != null) {
+            dialogIntent.setComponent(targetInfo.getComponentName());
+        } else {
+            Slog.wtf(TAG, "Failed to resolve intent for remote bugreport dialog");
+        }
+
         PendingIntent pendingDialogIntent = PendingIntent.getActivityAsUser(context, type,
                 dialogIntent, 0, null, UserHandle.CURRENT);
 
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 78439db..f0dca77 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -63,6 +63,7 @@
     static constexpr auto libDir = "lib"sv;
     static constexpr auto libSuffix = ".so"sv;
     static constexpr auto blockSize = 4096;
+    static constexpr auto systemPackage = "android"sv;
 };
 
 static const Constants& constants() {
@@ -377,7 +378,8 @@
         std::lock_guard l(mLock);
         mounts.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
-            if (ifs->mountId == id) {
+            if (ifs->mountId == id &&
+                ifs->dataLoaderStub->params().packageName == Constants::systemPackage) {
                 mounts.push_back(ifs);
             }
         }
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 17dd286..724048b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -204,7 +204,7 @@
     // Notification title and text for setManagedProfileMaximumTimeOff tests:
     private static final String PROFILE_OFF_SUSPENSION_TITLE = "suspension_title";
     private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text";
-    private static final String PROFILE_OFF_SUSPENSION_TOMORROW_TEXT = "suspension_tomorrow_text";
+    private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text";
 
     @Override
     protected void setUp() throws Exception {
@@ -6331,7 +6331,7 @@
         // Now the user should see a warning notification.
         verify(getServices().notificationManager, times(1))
                 .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE,
-                        EXTRA_TEXT, PROFILE_OFF_SUSPENSION_TOMORROW_TEXT)));
+                        EXTRA_TEXT, PROFILE_OFF_SUSPENSION_SOON_TEXT)));
         // Apps shouldn't be suspended yet.
         verifyZeroInteractions(getServices().ipackageManager);
         clearInvocations(getServices().alarmManager);
@@ -6482,7 +6482,7 @@
         // To allow creation of Notification via Notification.Builder
         mContext.applicationInfo = mRealTestContext.getApplicationInfo();
 
-        // Setup notification titles.
+        // Setup resources to render notification titles and texts.
         when(mServiceContext.resources
                 .getString(R.string.personal_apps_suspension_title))
                 .thenReturn(PROFILE_OFF_SUSPENSION_TITLE);
@@ -6490,14 +6490,19 @@
                 .getString(R.string.personal_apps_suspension_text))
                 .thenReturn(PROFILE_OFF_SUSPENSION_TEXT);
         when(mServiceContext.resources
-                .getString(R.string.personal_apps_suspension_tomorrow_text))
-                .thenReturn(PROFILE_OFF_SUSPENSION_TOMORROW_TEXT);
+                .getString(eq(R.string.personal_apps_suspension_soon_text),
+                        anyString(), anyString(), anyInt()))
+                .thenReturn(PROFILE_OFF_SUSPENSION_SOON_TEXT);
+
+        // Make locale available for date formatting:
+        when(mServiceContext.resources.getConfiguration())
+                .thenReturn(mRealTestContext.getResources().getConfiguration());
 
         clearInvocations(getServices().ipackageManager);
     }
 
     private static Matcher<Notification> hasExtra(String... extras) {
-        assertEquals("Odd numebr of extra key-values", 0, extras.length % 2);
+        assertEquals("Odd number of extra key-values", 0, extras.length % 2);
         return new BaseMatcher<Notification>() {
             @Override
             public boolean matches(Object item) {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 8137c36..43a396d 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.os.Handler;
@@ -28,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.display.DisplayModeDirector.BrightnessObserver;
 import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
 import com.android.server.display.DisplayModeDirector.Vote;
 
@@ -36,6 +38,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -52,16 +55,15 @@
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
     }
 
-    private DisplayModeDirector createDisplayModeDirectorWithDisplayFpsRange(
-            int minFps, int maxFps) {
+    private DisplayModeDirector createDirectorFromRefreshRateArray(
+            float[] refreshRates, int baseModeId) {
         DisplayModeDirector director =
                 new DisplayModeDirector(mContext, new Handler(Looper.getMainLooper()));
         int displayId = 0;
-        int numModes = maxFps - minFps + 1;
-        Display.Mode[] modes = new Display.Mode[numModes];
-        for (int i = minFps; i <= maxFps; i++) {
-            modes[i - minFps] = new Display.Mode(
-                    /*modeId=*/i, /*width=*/1000, /*height=*/1000, /*refreshRate=*/i);
+        Display.Mode[] modes = new Display.Mode[refreshRates.length];
+        for (int i = 0; i < refreshRates.length; i++) {
+            modes[i] = new Display.Mode(
+                    /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
         }
         SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
         supportedModesByDisplay.put(displayId, modes);
@@ -72,14 +74,22 @@
         return director;
     }
 
+    private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
+        int numRefreshRates = maxFps - minFps + 1;
+        float[] refreshRates = new float[numRefreshRates];
+        for (int i = 0; i < numRefreshRates; i++) {
+            refreshRates[i] = minFps + i;
+        }
+        return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps);
+    }
+
     @Test
     public void testDisplayModeVoting() {
         int displayId = 0;
 
         // With no votes present, DisplayModeDirector should allow any refresh rate.
         DesiredDisplayModeSpecs modeSpecs =
-                createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
-                        displayId);
+                createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(modeSpecs.baseModeId).isEqualTo(60);
         Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f);
         Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY);
@@ -92,7 +102,7 @@
         {
             int minFps = 60;
             int maxFps = 90;
-            DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+            DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
             assertTrue(2 * numPriorities < maxFps - minFps + 1);
             SparseArray<Vote> votes = new SparseArray<>();
             SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -114,7 +124,7 @@
         // presence of higher priority votes.
         {
             assertTrue(numPriorities >= 2);
-            DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+            DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
             SparseArray<Vote> votes = new SparseArray<>();
             SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
             votesByDisplay.put(displayId, votes);
@@ -131,7 +141,7 @@
     @Test
     public void testVotingWithFloatingPointErrors() {
         int displayId = 0;
-        DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(50, 90);
+        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(displayId, votes);
@@ -154,7 +164,7 @@
         assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_SIZE);
 
         int displayId = 0;
-        DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(displayId, votes);
@@ -202,7 +212,7 @@
                 >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
 
         int displayId = 0;
-        DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(displayId, votes);
@@ -235,4 +245,61 @@
                 .isWithin(FLOAT_TOLERANCE)
                 .of(75);
     }
+
+    void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
+            float peakFps, float defaultFps, float primaryMin, float primaryMax,
+            float appRequestMin, float appRequestMax) {
+        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
+                minFps, peakFps, defaultFps);
+        Truth.assertThat(specs.primaryRefreshRateRange.min).isEqualTo(primaryMin);
+        Truth.assertThat(specs.primaryRefreshRateRange.max).isEqualTo(primaryMax);
+        Truth.assertThat(specs.appRequestRefreshRateRange.min).isEqualTo(appRequestMin);
+        Truth.assertThat(specs.appRequestRefreshRateRange.max).isEqualTo(appRequestMax);
+    }
+
+    @Test
+    public void testSpecsFromRefreshRateSettings() {
+        // Confirm that, with varying settings for min, peak, and default refresh rate,
+        // DesiredDisplayModeSpecs is calculated correctly.
+        float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
+
+        float inf = Float.POSITIVE_INFINITY;
+        verifySpecsWithRefreshRateSettings(director, 0, 0, 0, 0, inf, 0, inf);
+        verifySpecsWithRefreshRateSettings(director, 0, 0, 90, 0, 90, 0, inf);
+        verifySpecsWithRefreshRateSettings(director, 0, 90, 0, 0, 90, 0, 90);
+        verifySpecsWithRefreshRateSettings(director, 0, 90, 60, 0, 60, 0, 90);
+        verifySpecsWithRefreshRateSettings(director, 0, 90, 120, 0, 90, 0, 90);
+        verifySpecsWithRefreshRateSettings(director, 90, 0, 0, 90, inf, 0, inf);
+        verifySpecsWithRefreshRateSettings(director, 90, 0, 120, 90, 120, 0, inf);
+        verifySpecsWithRefreshRateSettings(director, 90, 0, 60, 90, inf, 0, inf);
+        verifySpecsWithRefreshRateSettings(director, 90, 120, 0, 90, 120, 0, 120);
+        verifySpecsWithRefreshRateSettings(director, 90, 60, 0, 90, 90, 0, 90);
+        verifySpecsWithRefreshRateSettings(director, 60, 120, 90, 60, 90, 0, 120);
+    }
+
+    void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps,
+            float defaultFps, float brightnessObserverMin, float brightnessObserverMax) {
+        BrightnessObserver brightnessObserver = Mockito.mock(BrightnessObserver.class);
+        director.injectBrightnessObserver(brightnessObserver);
+        director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps);
+        verify(brightnessObserver)
+                .onRefreshRateSettingChangedLocked(brightnessObserverMin, brightnessObserverMax);
+    }
+
+    @Test
+    public void testBrightnessObserverCallWithRefreshRateSettings() {
+        // Confirm that, with varying settings for min, peak, and default refresh rate, we make the
+        // correct call to the brightness observer.
+        float[] refreshRates = {60.f, 90.f, 120.f};
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
+        verifyBrightnessObserverCall(director, 0, 0, 0, 0, 0);
+        verifyBrightnessObserverCall(director, 0, 0, 90, 0, 90);
+        verifyBrightnessObserverCall(director, 0, 90, 0, 0, 90);
+        verifyBrightnessObserverCall(director, 0, 90, 60, 0, 60);
+        verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
+        verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index efa25bd..320dacf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1582,6 +1582,41 @@
                 "s2");
     }
 
+    public void testCachedShortcuts_canPassShortcutLimit() {
+        // Change the max number of shortcuts.
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4");
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeLongLivedShortcut("s1"),
+                    makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
+                    makeLongLivedShortcut("s4"))));
+        });
+
+        // Cache All
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
+                    HANDLE_USER_0);
+        });
+
+        setCaller(CALLING_PACKAGE_1);
+
+        // Get dynamic shortcuts
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
+                "s1", "s2", "s3", "s4");
+        // Get cached shortcuts
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+                "s1", "s2", "s3", "s4");
+
+        assertTrue(mManager.setDynamicShortcuts(makeShortcuts("sx1", "sx2", "sx3", "sx4")));
+
+        // Get dynamic shortcuts
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
+                "sx1", "sx2", "sx3", "sx4");
+        // Get cached shortcuts
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+                "s1", "s2", "s3", "s4");
+    }
+
     // === Test for launcher side APIs ===
 
     public void testGetShortcuts() {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index e410220..2d45f9e 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -387,7 +387,7 @@
     @Test
     public void testBoundWidgetPackageExempt() throws Exception {
         assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null);
-        assertEquals(STANDBY_BUCKET_EXEMPTED,
+        assertEquals(STANDBY_BUCKET_ACTIVE,
                 mController.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID,
                         mInjector.mElapsedRealtime, false));
     }
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 289933e..d45ecc9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6650,18 +6650,14 @@
                 nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
-        assertTrue(mBinderService.hasSentMessage(PKG, mUid));
+        assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid));
     }
 
     @Test
     public void testRecordMessages_validMsg() throws RemoteException {
-        // Messaging notification with shortcut info
-        Notification.BubbleMetadata metadata =
-                new Notification.BubbleMetadata.Builder("id").build();
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
                 null /* groupKey */, false /* isSummary */);
-        nb.setShortcutId("id");
-        nb.setBubbleMetadata(metadata);
+        nb.setShortcutId(null);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
                 "testRecordMessages_validMsg", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -6670,7 +6666,43 @@
                 nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
-        assertFalse(mBinderService.hasSentMessage(PKG, mUid));
+        assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid));
+
+        nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testRecordMessages_validMsg");
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        assertFalse(mBinderService.isInInvalidMsgState(PKG, mUid));
+    }
+
+    @Test
+    public void testRecordMessages_invalidMsg_afterValidMsg() throws RemoteException {
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testRecordMessages_invalidMsg_afterValidMsg_1");
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+        assertTrue(mService.getNotificationRecord(nr.getKey()).isConversation());
+
+        mBinderService.cancelAllNotifications(PKG, mUid);
+        waitForIdle();
+
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setShortcutId(null);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(),
+                new UserHandle(mUid), null, 0);
+         nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        assertFalse(mService.getNotificationRecord(nr.getKey()).isConversation());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index b03596a..6df3c7b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -1137,6 +1137,26 @@
     }
 
     @Test
+    public void testIsConversation_noShortcut_appHasPreviousSentFullConversation() {
+        StatusBarNotification sbn = getMessagingStyleNotification();
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.setShortcutInfo(null);
+        record.setHasSentValidMsg(true);
+
+        assertFalse(record.isConversation());
+    }
+
+    @Test
+    public void testIsConversation_noShortcut_userDemotedApp() {
+        StatusBarNotification sbn = getMessagingStyleNotification();
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.setShortcutInfo(null);
+        record.userDemotedAppFromConvoSpace(true);
+
+        assertFalse(record.isConversation());
+    }
+
+    @Test
     public void testIsConversation_noShortcut_targetsR() {
         StatusBarNotification sbn = getMessagingStyleNotification(PKG_R);
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 4320f1c..078c21e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -81,6 +81,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
+import android.util.StatsEvent;
 import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
@@ -89,6 +90,7 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.UiServiceTestCase;
 
+
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.junit.Before;
@@ -454,7 +456,9 @@
         mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);
 
         mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
-        mHelper.setMessageSent(PKG_P, UID_P);
+        mHelper.setInvalidMessageSent(PKG_P, UID_P);
+        mHelper.setValidMessageSent(PKG_P, UID_P);
+        mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
 
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
 
@@ -470,8 +474,10 @@
 
         assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_O, UID_O));
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
-        assertTrue(mHelper.hasSentMessage(PKG_P, UID_P));
-        assertFalse(mHelper.hasSentMessage(PKG_N_MR1, UID_N_MR1));
+        assertTrue(mHelper.hasSentInvalidMsg(PKG_P, UID_P));
+        assertFalse(mHelper.hasSentInvalidMsg(PKG_N_MR1, UID_N_MR1));
+        assertTrue(mHelper.hasSentValidMsg(PKG_P, UID_P));
+        assertTrue(mHelper.didUserEverDemoteInvalidMsgApp(PKG_P, UID_P));
         assertEquals(channel1,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
         compareChannels(channel2,
@@ -2992,6 +2998,31 @@
                 PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
     }
 
+
+    @Test
+    public void testPullConversationNotificationChannel() {
+        String conversationId = "friend";
+
+        NotificationChannel parent =
+                new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        String channelId = String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId);
+        NotificationChannel friend = new NotificationChannel(channelId,
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parent.getId(), conversationId);
+        mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+        ArrayList<StatsEvent> events = new ArrayList<>();
+        mHelper.pullPackageChannelPreferencesStats(events);
+        boolean found = false;
+        for (StatsEvent event : events) {
+            // TODO(b/153195691): inspect the content once it is possible to do so
+            found = true;
+        }
+        assertTrue("conversation was not in the pull", found);
+    }
+
     @Test
     public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
         String conversationId = "friend";
@@ -3380,15 +3411,49 @@
     }
 
     @Test
-    public void testMessageSent() {
+    public void testInvalidMessageSent() {
         // create package preferences
         mHelper.canShowBadge(PKG_P, UID_P);
 
         // check default value
-        assertFalse(mHelper.hasSentMessage(PKG_P, UID_P));
+        assertFalse(mHelper.isInInvalidMsgState(PKG_P, UID_P));
 
         // change it
-        mHelper.setMessageSent(PKG_P, UID_P);
-        assertTrue(mHelper.hasSentMessage(PKG_P, UID_P));
+        mHelper.setInvalidMessageSent(PKG_P, UID_P);
+        assertTrue(mHelper.isInInvalidMsgState(PKG_P, UID_P));
+        assertTrue(mHelper.hasSentInvalidMsg(PKG_P, UID_P));
+    }
+
+    @Test
+    public void testValidMessageSent() {
+        // create package preferences
+        mHelper.canShowBadge(PKG_P, UID_P);
+
+        // get into the bad state
+        mHelper.setInvalidMessageSent(PKG_P, UID_P);
+
+        // and then fix it
+        mHelper.setValidMessageSent(PKG_P, UID_P);
+
+        assertTrue(mHelper.hasSentValidMsg(PKG_P, UID_P));
+        assertFalse(mHelper.isInInvalidMsgState(PKG_P, UID_P));
+    }
+
+    @Test
+    public void testUserDemotedInvalidMsgApp() {
+        // create package preferences
+        mHelper.canShowBadge(PKG_P, UID_P);
+
+        // demotion means nothing before msg notif sent
+        mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
+        assertFalse(mHelper.hasUserDemotedInvalidMsgApp(PKG_P, UID_P));
+
+        // it's valid when incomplete msgs have been sent
+        mHelper.setInvalidMessageSent(PKG_P, UID_P);
+        assertTrue(mHelper.hasUserDemotedInvalidMsgApp(PKG_P, UID_P));
+
+        // and is invalid once complete msgs are sent
+        mHelper.setValidMessageSent(PKG_P, UID_P);
+        assertFalse(mHelper.hasUserDemotedInvalidMsgApp(PKG_P, UID_P));
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 822cb5a..64e08c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -76,6 +76,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
 /**
  * Tests for the {@link ActivityStack} class.
  *
@@ -1327,6 +1330,8 @@
 
     @Test
     public void testCheckBehindFullscreenActivity() {
+        final ArrayList<ActivityRecord> occludedActivities = new ArrayList<>();
+        final Consumer<ActivityRecord> handleBehindFullscreenActivity = occludedActivities::add;
         final ActivityRecord bottomActivity =
                 new ActivityBuilder(mService).setStack(mStack).setTask(mTask).build();
         final ActivityRecord topActivity =
@@ -1337,12 +1342,21 @@
         assertFalse(mStack.checkBehindFullscreenActivity(topActivity,
                 null /* handleBehindFullscreenActivity */));
 
+        // Top activity occludes bottom activity.
+        mStack.checkBehindFullscreenActivity(null /* toCheck */, handleBehindFullscreenActivity);
+        assertThat(occludedActivities).containsExactly(bottomActivity);
+
         doReturn(false).when(topActivity).occludesParent();
         assertFalse(mStack.checkBehindFullscreenActivity(bottomActivity,
                 null /* handleBehindFullscreenActivity */));
         assertFalse(mStack.checkBehindFullscreenActivity(topActivity,
                 null /* handleBehindFullscreenActivity */));
 
+        occludedActivities.clear();
+        // Top activity doesn't occlude parent, so the bottom activity is not occluded.
+        mStack.checkBehindFullscreenActivity(null /* toCheck */, handleBehindFullscreenActivity);
+        assertThat(occludedActivities).isEmpty();
+
         final ActivityRecord finishingActivity =
                 new ActivityBuilder(mService).setStack(mStack).setTask(mTask).build();
         finishingActivity.finishing = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index edc9756..02d1f9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -49,6 +49,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -56,10 +57,10 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 
@@ -99,7 +100,6 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class ActivityStarterTests extends ActivityTestsBase {
-    private ActivityStarter mStarter;
     private ActivityStartController mController;
     private ActivityMetricsLogger mActivityMetricsLogger;
     private PackageManagerInternal mMockPackageManager;
@@ -127,8 +127,6 @@
         mController = mock(ActivityStartController.class);
         mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
         clearInvocations(mActivityMetricsLogger);
-        mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor,
-                mock(ActivityStartInterceptor.class));
     }
 
     @Test
@@ -181,6 +179,7 @@
      * {@link ActivityStarter#execute} based on these preconditions and ensures the result matches
      * the expected. It is important to note that the method also checks side effects of the start,
      * such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios.
+     *
      * @param preconditions A bitmask representing the preconditions for the launch
      * @param launchFlags The launch flags to be provided by the launch {@link Intent}.
      * @param expectedResult The expected result from the launch.
@@ -202,7 +201,7 @@
         final WindowProcessController wpc =
                 containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
                 ? null : new WindowProcessController(service, ai, null, 0, -1, null, listener);
-        doReturn(wpc).when(service).getProcessController(anyObject());
+        doReturn(wpc).when(service).getProcessController(any());
 
         final Intent intent = new Intent();
         intent.setFlags(launchFlags);
@@ -1034,4 +1033,46 @@
 
         verify(recentTasks, times(1)).add(any());
     }
+
+    @Test
+    public void testStartActivityInner_allSplitScreenPrimaryActivitiesVisible() {
+        // Given
+        final ActivityStarter starter = prepareStarter(0, false);
+
+        starter.setReason("testAllSplitScreenPrimaryActivitiesAreResumed");
+
+        final ActivityRecord targetRecord = new ActivityBuilder(mService).build();
+        targetRecord.setFocusable(false);
+        targetRecord.setVisibility(false);
+        final ActivityRecord sourceRecord = new ActivityBuilder(mService).build();
+
+        final ActivityStack stack = spy(
+                mRootWindowContainer.getDefaultTaskDisplayArea()
+                        .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+                                /* onTop */true));
+
+        stack.addChild(targetRecord);
+
+        doReturn(stack).when(mRootWindowContainer)
+                .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
+
+        starter.mStartActivity = new ActivityBuilder(mService).build();
+
+        // When
+        starter.startActivityInner(
+                /* r */targetRecord,
+                /* sourceRecord */ sourceRecord,
+                /* voiceSession */null,
+                /* voiceInteractor */ null,
+                /* startFlags */ 0,
+                /* doResume */true,
+                /* options */null,
+                /* inTask */null,
+                /* restrictedBgActivity */false);
+
+        // Then
+        verify(stack).ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        verify(targetRecord).makeVisibleIfNeeded(null, true);
+        assertTrue(targetRecord.mVisibleRequested);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 8f18f64..07050d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -52,6 +52,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.IWindowManager;
@@ -432,20 +433,35 @@
         removeGlobalMinSizeRestriction();
         final Rect stackBounds = new Rect(0, 0, 1000, 600);
         final Rect taskBounds = new Rect(100, 400, 600, 800);
-        mStack.setBounds(stackBounds);
-        mTask.setBounds(taskBounds);
+        // Set the bounds and windowing mode to window configuration directly, otherwise the
+        // testing setups may be discarded by configuration resolving.
+        mStack.getWindowConfiguration().setBounds(stackBounds);
+        mTask.getWindowConfiguration().setBounds(taskBounds);
+        mActivity.getWindowConfiguration().setBounds(taskBounds);
 
         // Check that anim bounds for freeform window match task bounds
-        mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
         assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_NONE));
 
         // STACK_CLIP_AFTER_ANIM should use task bounds since they will be clipped by
         // bounds animation layer.
-        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
 
+        // Even the activity is smaller than task and it is not aligned to the top-left corner of
+        // task, the animation bounds the same as task and position should be zero because in real
+        // case the letterbox will fill the remaining area in task.
+        final Rect halfBounds = new Rect(taskBounds);
+        halfBounds.scale(0.5f);
+        mActivity.getWindowConfiguration().setBounds(halfBounds);
+        final Point animationPosition = new Point();
+        mActivity.getAnimationPosition(animationPosition);
+
+        assertEquals(taskBounds, mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+        assertEquals(new Point(0, 0), animationPosition);
+
         // STACK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later.
-        mTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         assertEquals(mStack.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
index 6a1f50d..f4b50dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.RemoteException;
@@ -91,5 +92,11 @@
 
         mDisplayContent.setBounds(new Rect(0, 0, 1000, 1000));
         verify(organizer).onDisplayAreaInfoChanged(any());
+
+        Configuration tmpConfiguration = new Configuration();
+        tmpConfiguration.setTo(mDisplayContent.getRequestedOverrideConfiguration());
+        mDisplayContent.onRequestedOverrideConfigurationChanged(tmpConfiguration);
+        // Ensure it was still only called once if the bounds didn't change
+        verify(organizer).onDisplayAreaInfoChanged(any());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c2db0c0..a3f9b2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.Surface.ROTATION_0;
 import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -56,7 +58,10 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
 import android.view.InsetsState;
+import android.view.WindowInsets.Side;
 import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
@@ -352,4 +357,36 @@
         insetsPolicy.updateBarControlTarget(mAppWindow);
         assertNull(displayPolicy.mInputConsumer);
     }
+
+    @Test
+    public void testImeMinimalSourceFrame() {
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1000;
+        displayInfo.logicalHeight = 2000;
+        displayInfo.rotation = ROTATION_0;
+        mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
+                displayInfo, null /* displayCutout */);
+
+        displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+        mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
+
+        mDisplayContent.setInputMethodWindowLocked(mImeWindow);
+        mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
+        mImeWindow.getGivenContentInsetsLw().set(0, displayInfo.logicalHeight, 0, 0);
+        mImeWindow.getControllableInsetProvider().setServerVisible(true);
+
+        displayPolicy.beginLayoutLw(mDisplayContent.mDisplayFrames, 0 /* UI mode */);
+        displayPolicy.layoutWindowLw(mImeWindow, null, mDisplayContent.mDisplayFrames);
+
+        final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+        final InsetsSource imeSource = state.peekSource(ITYPE_IME);
+        final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+
+        assertNotNull(imeSource);
+        assertNotNull(navBarSource);
+        assertFalse(imeSource.getFrame().isEmpty());
+        assertFalse(navBarSource.getFrame().isEmpty());
+        assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 88de34d..bdcae48 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -24,6 +24,7 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -39,10 +40,15 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.view.Surface;
 
+import com.android.server.LocalServices;
+
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 
 import java.io.File;
 import java.util.function.Predicate;
@@ -70,11 +76,26 @@
         mLowResScale = lowResScale;
     }
 
+    @BeforeClass
+    public static void setUpOnce() {
+        final UserManagerInternal userManager = mock(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, userManager);
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+    }
+
     @Before
     public void setUp() {
         final UserManager um = UserManager.get(getInstrumentation().getTargetContext());
         mTestUserId = um.getUserHandle();
 
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        when(userManagerInternal.isUserUnlocked(mTestUserId)).thenReturn(true);
+
         mContextSpy = spy(new ContextWrapper(mWm.mContext));
         mResourcesSpy = spy(mContextSpy.getResources());
         when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 42e2bbf..6c13cd7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -346,12 +346,15 @@
             sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
                     + soundModelId));
 
-            // Unload the model if it is loaded.
-            mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
-            mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
+            if (isInitialized()) {
+                // Unload the model if it is loaded.
+                mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
 
-            // Stop recognition if it is started.
-            mSoundModelStatTracker.onStop(soundModelId.getUuid());
+                // Stop tracking recognition if it is started.
+                mSoundModelStatTracker.onStop(soundModelId.getUuid());
+            }
+
+            mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
         }
 
         @Override
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ead90bb..3365ab7 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -1635,13 +1636,21 @@
 
     /**
      * Instructs Telecom to put the call into the background audio processing state.
-     *
+     * <p>
      * This method can be called either when the call is in {@link #STATE_RINGING} or
      * {@link #STATE_ACTIVE}. After Telecom acknowledges the request by setting the call's state to
      * {@link #STATE_AUDIO_PROCESSING}, your app may setup the audio paths with the audio stack in
      * order to capture and play audio on the call stream.
-     *
+     * <p>
      * This method can only be called by the default dialer app.
+     * <p>
+     * Apps built with SDK version {@link android.os.Build.VERSION_CODES#R} or later which are using
+     * the microphone as part of audio processing should specify the foreground service type using
+     * the attribute {@link android.R.attr#foregroundServiceType} in the {@link InCallService}
+     * service element of the app's manifest file.
+     * The {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE} attribute should be specified.
+     * @see <a href="https://developer.android.com/preview/privacy/foreground-service-types">
+     * the Android Developer Site</a> for more information.
      * @hide
      */
     @SystemApi
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index f8722f4..8abab90 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -23,6 +23,7 @@
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -281,9 +282,20 @@
              * Sets whether to request background audio processing so that the in-call service can
              * screen the call further. If set to {@code true}, {@link #setDisallowCall} should be
              * called with {@code false}, and all other parameters in this builder will be ignored.
-             *
+             * <p>
              * This request will only be honored if the {@link CallScreeningService} shares the same
              * uid as the default dialer app. Otherwise, the call will go through as usual.
+             * <p>
+             * Apps built with SDK version {@link android.os.Build.VERSION_CODES#R} or later which
+             * are using the microphone as part of audio processing should specify the
+             * foreground service type using the attribute
+             * {@link android.R.attr#foregroundServiceType} in the {@link CallScreeningService}
+             * service element of the app's manifest file.
+             * The {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE} attribute should be
+             * specified.
+             * @see
+             * <a href="https://developer.android.com/preview/privacy/foreground-service-types">
+             *     the Android Developer Site</a> for more information.
              *
              * @param shouldScreenCallViaAudioProcessing Whether to request further call screening.
              * @hide
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index bb6f154..b35b323 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -536,13 +536,16 @@
 
         // Assign permission to special system apps
         assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
-                PHONE_PACKAGE_NAME);
+                PHONE_PACKAGE_NAME, true);
         assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
-                BLUETOOTH_PACKAGE_NAME);
+                BLUETOOTH_PACKAGE_NAME, true);
         assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
-                MMS_SERVICE_PACKAGE_NAME);
+                MMS_SERVICE_PACKAGE_NAME, true);
         assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
-                TELEPHONY_PROVIDER_PACKAGE_NAME);
+                TELEPHONY_PROVIDER_PACKAGE_NAME, true);
+        // CellbroadcastReceiver is a mainline module thus skip signature match.
+        assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
+                CellBroadcastUtils.getDefaultCellBroadcastReceiverPackageName(context), false);
 
         // Give AppOps permission to UID 1001 which contains multiple
         // apps, all of them should be able to write to telephony provider.
@@ -744,17 +747,23 @@
      * @param packageManager The package manager instance
      * @param appOps The AppOps manager instance
      * @param packageName The package name of the system app
+     * @param sigatureMatch whether to check signature match
      */
     private static void assignExclusiveSmsPermissionsToSystemApp(Context context,
-            PackageManager packageManager, AppOpsManager appOps, String packageName) {
+            PackageManager packageManager, AppOpsManager appOps, String packageName,
+            boolean sigatureMatch) {
         // First check package signature matches the caller's package signature.
         // Since this class is only used internally by the system, this check makes sure
         // the package signature matches system signature.
-        final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
-        if (result != PackageManager.SIGNATURE_MATCH) {
-            Log.e(LOG_TAG, packageName + " does not have system signature");
-            return;
+        if (sigatureMatch) {
+            final int result = packageManager.checkSignatures(context.getPackageName(),
+                    packageName);
+            if (result != PackageManager.SIGNATURE_MATCH) {
+                Log.e(LOG_TAG, packageName + " does not have system signature");
+                return;
+            }
         }
+
         try {
             PackageInfo info = packageManager.getPackageInfo(packageName, 0);
             int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid,
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 3546434..d62cd0a 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -837,20 +837,20 @@
                 + " carrierId=" + mCarrierId + " displayName=" + mDisplayName
                 + " carrierName=" + mCarrierName + " nameSource=" + mNameSource
                 + " iconTint=" + mIconTint
-                + " mNumber=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
-                + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
-                + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
-                + " nativeAccessRules " + Arrays.toString(mNativeAccessRules)
+                + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
+                + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc=" + mMcc
+                + " mnc=" + mMnc + " countryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded
+                + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules)
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
-                + " isOpportunistic=" + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
-                + " mIsGroupDisabled=" + mIsGroupDisabled
+                + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID
+                + " isGroupDisabled=" + mIsGroupDisabled
                 + " profileClass=" + mProfileClass
                 + " ehplmns=" + Arrays.toString(mEhplmns)
                 + " hplmns=" + Arrays.toString(mHplmns)
                 + " subscriptionType=" + mSubscriptionType
-                + " mGroupOwner=" + mGroupOwner
+                + " groupOwner=" + mGroupOwner
                 + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
-                + " mAreUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
+                + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index ede67dd..94407f1 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -56,14 +56,15 @@
      * Activity Action: Show the opt-in dialog for enabling or disabling RCS contact discovery
      * using User Capability Exchange (UCE).
      * <p>
-     * An application that depends on contact discovery being enabled may send this intent
+     * An application that depends on RCS contact discovery being enabled must send this intent
      * using {@link Context#startActivity(Intent)} to ask the user to opt-in for contacts upload for
-     * capability exchange if it is currently disabled. Whether or not this setting has been enabled
-     * can be queried using {@link RcsUceAdapter#isUceSettingEnabled()}.
+     * capability exchange if it is currently disabled. Whether or not RCS contact discovery has
+     * been enabled by the user can be queried using {@link RcsUceAdapter#isUceSettingEnabled()}.
      * <p>
-     * This intent should only be sent if the carrier supports RCS capability exchange, which can be
-     * queried using the key {@link CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL}. Otherwise, the
-     * setting will not be present.
+     * This intent will always be handled by the system, however the application should only send
+     * this Intent if the carrier supports RCS contact discovery, which can be queried using the key
+     * {@link CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL}. Otherwise, the RCS contact discovery
+     * opt-in dialog will not be shown.
      * <p>
      * Input: A mandatory {@link Settings#EXTRA_SUB_ID} extra containing the subscription that the
      * setting will be be shown for.
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 05ab6bd..ec11279 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -363,9 +363,10 @@
     /**
      * Change the user’s setting for whether or not UCE is enabled for the associated subscription.
      * <p>
-     * If an application Requires UCE, they may launch an Activity using the Intent
+     * If an application Requires UCE, they will launch an Activity using the Intent
      * {@link ImsRcsManager#ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN}, which will ask the user if
-     * they wish to enable this feature.
+     * they wish to enable this feature. This setting should only be enabled after the user has
+     * opted-in to capability exchange.
      * <p>
      * Note: This setting does not affect whether or not the device publishes its service
      * capabilities if the subscription supports presence publication.
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index fe0224a..ebe9b57 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -222,6 +222,10 @@
         return sLoopers.get(test);
     }
 
+    public static void remove(Object test) {
+        sLoopers.remove(test);
+    }
+
     static class LooperFrameworkMethod extends FrameworkMethod {
         private HandlerThread mHandlerThread;
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7d20d0d..5f46cb3 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -817,6 +817,9 @@
     /**
      * Broadcast intent action indicating that the wifi network settings
      * had been reset.
+     *
+     * Note: This intent is sent as a directed broadcast to each manifest registered receiver.
+     * Intent will not be received by dynamically registered receivers.
      * @hide
      */
     @SystemApi