Merge "Fix Ime consumer isRequestedVisible" into rvc-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index 755380e..777cbc54 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -126,6 +126,7 @@
 
   public class ActivityTaskManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(java.util.List<java.lang.String>);
+    method public static boolean currentUiModeSupportsErrorDialogs(@NonNull android.content.Context);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public String listAllStacks();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToStack(int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index e251399..ba4cf11 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -382,10 +382,6 @@
 
     inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); }
 
-    inline void setResetState(int32_t resetState) {
-        mResetState = resetState;
-    }
-
     // Default value = false
     inline bool isNested() const { return getValueFromBitmask(NESTED_POS); }
 
@@ -398,12 +394,6 @@
     // Default value = false
     inline bool isUidField() const { return getValueFromBitmask(UID_POS); }
 
-    // If a reset state is not sent in the StatsEvent, returns -1. Note that a
-    // reset satate is only sent if and only if a reset should be triggered.
-    inline int32_t getResetState() const {
-        return mResetState;
-    }
-
 private:
     inline void setBitmaskAtPos(int pos, bool value) {
         mBooleanBitmask &= ~(1 << pos); // clear
@@ -417,8 +407,6 @@
     // This is a bitmask over all annotations stored in boolean form. Because
     // there are only 4 booleans, just one byte is required.
     uint8_t mBooleanBitmask = 0;
-
-    int32_t mResetState = -1;
 };
 
 /**
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index d914ab2..f91c600 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -389,15 +389,24 @@
 void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
+    // Tell StatsdStats about new event
+    const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
+    int atomId = event->GetTagId();
+    StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC);
+    if (!event->isValid()) {
+        StatsdStats::getInstance().noteAtomError(atomId);
+        return;
+    }
+
     // Hard-coded logic to update train info on disk and fill in any information
     // this log event may be missing.
-    if (event->GetTagId() == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) {
+    if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) {
         onBinaryPushStateChangedEventLocked(event);
     }
 
     // Hard-coded logic to update experiment ids on disk for certain rollback
     // types and fill the rollback atom with experiment ids
-    if (event->GetTagId() == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) {
+    if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) {
         onWatchdogRollbackOccurredLocked(event);
     }
 
@@ -406,16 +415,11 @@
         ALOGI("%s", event->ToString().c_str());
     }
 #endif
-    const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
-
     resetIfConfigTtlExpiredLocked(eventElapsedTimeNs);
 
-    StatsdStats::getInstance().noteAtomLogged(
-        event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC);
-
     // Hard-coded logic to update the isolated uid's in the uid-map.
     // The field numbers need to be currently updated by hand with atoms.proto
-    if (event->GetTagId() == android::os::statsd::util::ISOLATED_UID_CHANGED) {
+    if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) {
         onIsolatedUidChangedEventLocked(*event);
     }
 
@@ -432,7 +436,7 @@
     }
 
 
-    if (event->GetTagId() != android::os::statsd::util::ISOLATED_UID_CHANGED) {
+    if (atomId != android::os::statsd::util::ISOLATED_UID_CHANGED) {
         // Map the isolated uid to host uid if necessary.
         mapIsolatedUidToHostUidIfNecessaryLocked(event);
     }
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 933f48d..3618bb0 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -67,8 +67,14 @@
                     lock_guard<mutex> lk(*cv_mutex);
                     for (const StatsEventParcel& parcel: output) {
                         shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1);
-                        event->parseBuffer((uint8_t*)parcel.buffer.data(), parcel.buffer.size());
-                        sharedData->push_back(event);
+                        bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(),
+                                                        parcel.buffer.size());
+                        if (valid) {
+                            sharedData->push_back(event);
+                        } else {
+                            StatsdStats::getInstance().noteAtomError(event->GetTagId(),
+                                                                     /*pull=*/true);
+                        }
                     }
                     *pullSuccess = success;
                     *pullFinish = true;
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index db637b1..46f5dbd 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -54,6 +54,7 @@
 
 const int FIELD_ID_ATOM_STATS_TAG = 1;
 const int FIELD_ID_ATOM_STATS_COUNT = 2;
+const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3;
 
 const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
 const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1;
@@ -549,6 +550,20 @@
             std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs);
 }
 
+void StatsdStats::noteAtomError(int atomTag, bool pull) {
+    lock_guard<std::mutex> lock(mLock);
+    if (pull) {
+        mPulledAtomStats[atomTag].atomErrorCount++;
+        return;
+    }
+
+    bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end());
+    bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize);
+    if (!full || present) {
+        mPushedAtomErrorStats[atomTag]++;
+    }
+}
+
 StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) {
     auto atomMetricStatsIter = mAtomMetricStats.find(metricId);
     if (atomMetricStatsIter != mAtomMetricStats.end()) {
@@ -604,9 +619,11 @@
         pullStats.second.pullExceedMaxDelay = 0;
         pullStats.second.registeredCount = 0;
         pullStats.second.unregisteredCount = 0;
+        pullStats.second.atomErrorCount = 0;
     }
     mAtomMetricStats.clear();
     mActivationBroadcastGuardrailStats.clear();
+    mPushedAtomErrorStats.clear();
 }
 
 string buildTimeString(int64_t timeSec) {
@@ -617,6 +634,15 @@
     return string(timeBuffer);
 }
 
+int StatsdStats::getPushedAtomErrors(int atomId) const {
+    const auto& it = mPushedAtomErrorStats.find(atomId);
+    if (it != mPushedAtomErrorStats.end()) {
+        return it->second;
+    } else {
+        return 0;
+    }
+}
+
 void StatsdStats::dumpStats(int out) const {
     lock_guard<std::mutex> lock(mLock);
     time_t t = mStartTimeSec;
@@ -721,11 +747,13 @@
     const size_t atomCounts = mPushedAtomStats.size();
     for (size_t i = 2; i < atomCounts; i++) {
         if (mPushedAtomStats[i] > 0) {
-            dprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]);
+            dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i],
+                    getPushedAtomErrors((int)i));
         }
     }
     for (const auto& pair : mNonPlatformPushedAtomStats) {
-        dprintf(out, "Atom %lu->%d\n", (unsigned long)pair.first, pair.second);
+        dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second,
+                getPushedAtomErrors(pair.first));
     }
 
     dprintf(out, "********Pulled Atom stats***********\n");
@@ -737,13 +765,15 @@
                 "nanos)%lld, "
                 "  (max pull delay nanos)%lld, (data error)%ld\n"
                 "  (pull timeout)%ld, (pull exceed max delay)%ld\n"
-                "  (registered count) %ld, (unregistered count) %ld\n",
+                "  (registered count) %ld, (unregistered count) %ld\n"
+                "  (atom error count) %d\n",
                 (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache,
                 (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec,
                 (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs,
                 (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs,
                 pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay,
-                pair.second.registeredCount, pair.second.unregisteredCount);
+                pair.second.registeredCount, pair.second.unregisteredCount,
+                pair.second.atomErrorCount);
     }
 
     if (mAnomalyAlarmRegisteredStats > 0) {
@@ -919,6 +949,10 @@
                     proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
             proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
             proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
+            int errors = getPushedAtomErrors(i);
+            if (errors > 0) {
+                proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+            }
             proto.end(token);
         }
     }
@@ -928,6 +962,10 @@
                 proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first);
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second);
+        int errors = getPushedAtomErrors(pair.first);
+        if (errors > 0) {
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+        }
         proto.end(token);
     }
 
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index ff31e9e..21e524a 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -461,6 +461,16 @@
      */
      void noteActivationBroadcastGuardrailHit(const int uid);
 
+     /**
+      * Reports that an atom is erroneous or cannot be parsed successfully by
+      * statsd. An atom tag of 0 indicates that the client did not supply the
+      * atom id within the encoding.
+      *
+      * For pushed atoms only, this call should be preceded by a call to
+      * noteAtomLogged.
+      */
+     void noteAtomError(int atomTag, bool pull=false);
+
     /**
      * Reset the historical stats. Including all stats in icebox, and the tracked stats about
      * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
@@ -499,6 +509,7 @@
         long emptyData = 0;
         long registeredCount = 0;
         long unregisteredCount = 0;
+        int32_t atomErrorCount = 0;
     } PulledAtomStats;
 
     typedef struct {
@@ -546,6 +557,12 @@
     // Maps PullAtomId to its stats. The size is capped by the puller atom counts.
     std::map<int, PulledAtomStats> mPulledAtomStats;
 
+    // Stores the number of times a pushed atom was logged erroneously. The
+    // corresponding counts for pulled atoms are stored in PulledAtomStats.
+    // The max size of this map is kMaxAtomErrorsStatsSize.
+    std::map<int, int> mPushedAtomErrorStats;
+    int kMaxPushedAtomErrorStatsSize = 100;
+
     // Maps metric ID to its stats. The size is capped by the number of metrics.
     std::map<int64_t, AtomMetricStats> mAtomMetricStats;
 
@@ -613,6 +630,8 @@
 
     void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats);
 
+    int getPushedAtomErrors(int atomId) const;
+
     /**
      * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference
      * will live as long as `this`.
@@ -631,6 +650,7 @@
     FRIEND_TEST(StatsdStatsTest, TestPullAtomStats);
     FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
     FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
+    FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 61cd017..eb830e1 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -114,14 +114,6 @@
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
 }
 
-LogEvent::~LogEvent() {
-    if (mContext) {
-        // This is for the case when LogEvent is created using the test interface
-        // but init() isn't called.
-        android_log_destroy(&mContext);
-    }
-}
-
 void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     int32_t value = readNextValue<int32_t>();
     addToValues(pos, depth, value, last);
@@ -303,8 +295,7 @@
         return;
     }
 
-    int32_t resetState = readNextValue<int32_t>();
-    mValues[mValues.size() - 1].mAnnotations.setResetState(resetState);
+    mResetState = readNextValue<int32_t>();
 }
 
 void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
@@ -386,7 +377,6 @@
         typeInfo = readNextValue<uint8_t>();
         uint8_t typeId = getTypeId(typeInfo);
 
-        // TODO(b/144373276): handle errors passed to the socket
         switch (typeId) {
             case BOOL_TYPE:
                 parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
@@ -413,8 +403,13 @@
                 parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0];
                 break;
+            case ERROR_TYPE:
+                mErrorBitmask = readNextValue<int32_t>();
+                mValid = false;
+                break;
             default:
                 mValid = false;
+                break;
         }
     }
 
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 41fdcc2..dedcfaf 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -70,7 +70,7 @@
     explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                       const InstallTrainInfo& installTrainInfo);
 
-    ~LogEvent();
+    ~LogEvent() {}
 
     /**
      * Get the timestamp associated with this event.
@@ -184,6 +184,12 @@
         return mExclusiveStateFieldIndex;
     }
 
+    // If a reset state is not sent in the StatsEvent, returns -1. Note that a
+    // reset state is sent if and only if a reset should be triggered.
+    inline int getResetState() const {
+        return mResetState;
+    }
+
     inline LogEvent makeCopy() {
         return LogEvent(*this);
     }
@@ -204,6 +210,14 @@
         return BAD_INDEX;
     }
 
+    bool isValid() const {
+        return mValid;
+    }
+
+    int32_t getErrorBitmask() const {
+        return mErrorBitmask;
+    }
+
 private:
     /**
      * Only use this if copy is absolutely needed.
@@ -230,12 +244,13 @@
     bool checkPreviousValueType(Type expected);
 
     /**
-     * The below three variables are only valid during the execution of
+     * The below two variables are only valid during the execution of
      * parseBuffer. There are no guarantees about the state of these variables
      * before/after.
      */
     uint8_t* mBuf;
     uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed
+
     bool mValid = true; // stores whether the event we received from the socket is valid
 
     /**
@@ -287,19 +302,15 @@
     // matching.
     std::vector<FieldValue> mValues;
 
-    // This field is used when statsD wants to create log event object and write fields to it. After
-    // calling init() function, this object would be destroyed to save memory usage.
-    // When the log event is created from log msg, this field is never initiated.
-    android_log_context mContext = NULL;
-
     // The timestamp set by the logd.
     int64_t mLogdTimestampNs;
 
     // The elapsed timestamp set by statsd log writer.
     int64_t mElapsedTimestampNs;
 
-    // The atom tag of the event.
-    int mTagId;
+    // The atom tag of the event (defaults to 0 if client does not
+    // appropriately set the atom id).
+    int mTagId = 0;
 
     // The uid of the logging client (defaults to -1).
     int32_t mLogUid = -1;
@@ -307,11 +318,15 @@
     // The pid of the logging client (defaults to -1).
     int32_t mLogPid = -1;
 
+    // Bitmask of errors sent by StatsEvent/AStatsEvent.
+    int32_t mErrorBitmask = 0;
+
     // Annotations
     bool mTruncateTimestamp = false;
     int mUidFieldIndex = -1;
     int mAttributionChainIndex = -1;
     int mExclusiveStateFieldIndex = -1;
+    int mResetState = -1;
 };
 
 void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index b7f314a..b63713b 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -51,7 +51,7 @@
         return;
     }
 
-    const int32_t resetState = stateValue.mAnnotations.getResetState();
+    const int32_t resetState = event.getResetState();
     if (resetState != -1) {
         VLOG("StateTracker new reset state: %d", resetState);
         handleReset(eventTimeNs, resetState);
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index f4247ec..868247b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -425,6 +425,7 @@
     message AtomStats {
         optional int32 tag = 1;
         optional int32 count = 2;
+        optional int32 error_count = 3;
     }
 
     repeated AtomStats atom_stats = 7;
@@ -460,6 +461,7 @@
         optional int64 empty_data = 15;
         optional int64 registered_count = 16;
         optional int64 unregistered_count = 17;
+        optional int32 atom_error_count = 18;
     }
     repeated PulledAtomStats pulled_atom_stats = 10;
 
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 5635313..f9fddc8 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -80,6 +80,8 @@
 const int FIELD_ID_EMPTY_DATA = 15;
 const int FIELD_ID_PULL_REGISTERED_COUNT = 16;
 const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17;
+const int FIELD_ID_ATOM_ERROR_COUNT = 18;
+
 // for AtomMetricStats proto
 const int FIELD_ID_ATOM_METRIC_STATS = 17;
 const int FIELD_ID_METRIC_ID = 1;
@@ -492,6 +494,7 @@
                        (long long) pair.second.registeredCount);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT,
                        (long long) pair.second.unregisteredCount);
+    protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount);
     protoOutput->end(token);
 }
 
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index bb4578d..e52e2d0 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -352,7 +352,7 @@
 
     const vector<FieldValue>& values = event.getValues();
     EXPECT_EQ(values.size(), 1);
-    EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState);
+    EXPECT_EQ(event.getResetState(), resetState);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 129fafa..cdde603 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -486,6 +486,41 @@
     EXPECT_TRUE(uid2Good);
 }
 
+TEST(StatsdStatsTest, TestAtomErrorStats) {
+    StatsdStats stats;
+
+    int pushAtomTag = 100;
+    int pullAtomTag = 1000;
+    int numErrors = 10;
+
+    for (int i = 0; i < numErrors; i++) {
+        // We must call noteAtomLogged as well because only those pushed atoms
+        // that have been logged will have stats printed about them in the
+        // proto.
+        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0);
+        stats.noteAtomError(pushAtomTag, /*pull=*/false);
+
+        stats.noteAtomError(pullAtomTag, /*pull=*/true);
+    }
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+    // Check error count = numErrors for push atom
+    EXPECT_EQ(1, report.atom_stats_size());
+    const auto& pushedAtomStats = report.atom_stats(0);
+    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+    EXPECT_EQ(numErrors, pushedAtomStats.error_count());
+
+    // Check error count = numErrors for pull atom
+    EXPECT_EQ(1, report.pulled_atom_stats_size());
+    const auto& pulledAtomStats = report.pulled_atom_stats(0);
+    EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id());
+    EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index 9a3dad2..623734e 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -30,7 +30,6 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Xml;
 
@@ -153,7 +152,7 @@
                     com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity);
             asAttributes.recycle();
 
-            if (mDescriptionResId == 0 || mSummaryResId == 0) {
+            if ((mDescriptionResId == 0 && mHtmlDescriptionRes == 0) || mSummaryResId == 0) {
                 throw new XmlPullParserException("No description or summary in meta-data");
             }
         } catch (PackageManager.NameNotFoundException e) {
@@ -243,7 +242,10 @@
     public String loadHtmlDescription(@NonNull PackageManager packageManager) {
         final String htmlDescription = loadResourceString(packageManager, mActivityInfo,
                 mHtmlDescriptionRes);
-        return TextUtils.isEmpty(htmlDescription) ? null : getFilteredHtmlText(htmlDescription);
+        if (htmlDescription != null) {
+            return getFilteredHtmlText(htmlDescription);
+        }
+        return null;
     }
 
     /**
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 1b941de..1cc63da 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -23,8 +23,10 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -430,4 +432,14 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** Returns whether the current UI mode supports error dialogs (ANR, crash, etc). */
+    public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) {
+        final Configuration config = context.getResources().getConfiguration();
+        int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
+        return (modeType != Configuration.UI_MODE_TYPE_CAR
+                && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
+                && modeType != Configuration.UI_MODE_TYPE_TELEVISION
+                && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
+    }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1dadbda..85bafd9 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1695,7 +1695,7 @@
         }
 
         // Check to see if overlay should be excluded based on system property condition
-        if (!checkRequiredSystemProperty(requiredSystemPropertyName,
+        if (!checkRequiredSystemProperties(requiredSystemPropertyName,
                 requiredSystemPropertyValue)) {
             Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
                     + codePath + ": overlay ignored due to required system property: "
@@ -1997,7 +1997,7 @@
                 }
 
                 // check to see if overlay should be excluded based on system property condition
-                if (!checkRequiredSystemProperty(propName, propValue)) {
+                if (!checkRequiredSystemProperties(propName, propValue)) {
                     Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
                         + pkg.baseCodePath+ ": overlay ignored due to required system property: "
                         + propName + " with value: " + propValue);
@@ -2427,24 +2427,42 @@
 
     /**
      * Returns {@code true} if both the property name and value are empty or if the given system
-     * property is set to the specified value. In all other cases, returns {@code false}
+     * property is set to the specified value. Properties can be one or more, and if properties are
+     * more than one, they must be separated by comma, and count of names and values must be equal,
+     * and also every given system property must be set to the corresponding value.
+     * In all other cases, returns {@code false}
      */
-    public static boolean checkRequiredSystemProperty(String propName, String propValue) {
-        if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
-            if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
+    public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames,
+            @Nullable String rawPropValues) {
+        if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) {
+            if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) {
                 // malformed condition - incomplete
-                Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName
-                    + "=" + propValue + "' - require both requiredSystemPropertyName"
-                    + " AND requiredSystemPropertyValue to be specified.");
+                Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames
+                        + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+                        + " AND requiredSystemPropertyValue to be specified.");
                 return false;
             }
             // no valid condition set - so no exclusion criteria, overlay will be included.
             return true;
         }
 
-        // check property value - make sure it is both set and equal to expected value
-        final String currValue = SystemProperties.get(propName);
-        return (currValue != null && currValue.equals(propValue));
+        final String[] propNames = rawPropNames.split(",");
+        final String[] propValues = rawPropValues.split(",");
+
+        if (propNames.length != propValues.length) {
+            Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames
+                    + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+                    + " AND requiredSystemPropertyValue lists to have the same size.");
+            return false;
+        }
+        for (int i = 0; i < propNames.length; i++) {
+            // Check property value: make sure it is both set and equal to expected value
+            final String currValue = SystemProperties.get(propNames[i]);
+            if (!TextUtils.equals(currValue, propValues[i])) {
+                return false;
+            }
+        }
+        return true;
     }
 
     /**
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 27399e4..2f416a2 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -413,7 +413,7 @@
         }
 
         // Check to see if overlay should be excluded based on system property condition
-        if (!PackageParser.checkRequiredSystemProperty(requiredSystemPropertyName,
+        if (!PackageParser.checkRequiredSystemProperties(requiredSystemPropertyName,
                 requiredSystemPropertyValue)) {
             Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
                     + codePath + ": overlay ignored due to required system property: "
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 29ece49..88f4c31 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -84,7 +84,6 @@
 import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.ext.SdkExtensions;
 import android.text.TextUtils;
@@ -2348,7 +2347,7 @@
                     R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName);
             String propValue = sa.getString(
                     R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue);
-            if (!checkOverlayRequiredSystemProperty(propName, propValue)) {
+            if (!PackageParser.checkRequiredSystemProperties(propName, propValue)) {
                 Slog.i(TAG, "Skipping target and overlay pair " + target + " and "
                         + pkg.getBaseCodePath()
                         + ": overlay ignored due to required system property: "
@@ -2522,24 +2521,6 @@
         }
     }
 
-    private static boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
-        if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
-            if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
-                // malformed condition - incomplete
-                Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName
-                        + "=" + propValue + "' - require both requiredSystemPropertyName"
-                        + " AND requiredSystemPropertyValue to be specified.");
-                return false;
-            }
-            // no valid condition set - so no exclusion criteria, overlay will be included.
-            return true;
-        }
-
-        // check property value - make sure it is both set and equal to expected value
-        final String currValue = SystemProperties.get(propName);
-        return (currValue != null && currValue.equals(propValue));
-    }
-
     /**
      * This is a pre-density application which will get scaled - instead of being pixel perfect.
      * This type of application is not resizable.
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index aee32ed..a1a11ed 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2447,7 +2447,12 @@
         final String filePath = path.getCanonicalPath();
         final StorageVolume volume = getStorageVolume(path);
         if (volume == null) {
-            throw new IllegalStateException("Failed to update quota type for " + filePath);
+            Log.w(TAG, "Failed to update quota type for " + filePath);
+            return;
+        }
+        if (!volume.isEmulated()) {
+            // We only support quota tracking on emulated filesystems
+            return;
         }
 
         final int userId = volume.getOwner().getIdentifier();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc380f3..e4dbd637 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -514,33 +514,24 @@
     int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3;
 
     /**
-     * Parcel key for the screen shot bitmap sent with messages of type
-     * {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}, type {@link android.graphics.Bitmap}
+     * Enum listing the possible sources from which a screenshot was originated. Used for logging.
+     *
      * @hide
      */
-    String PARCEL_KEY_SCREENSHOT_BITMAP = "screenshot_screen_bitmap";
-
-    /**
-     * Parcel key for the screen bounds of the image sent with messages of type
-     * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link Rect} in screen coordinates.
-     * @hide
-     */
-    String PARCEL_KEY_SCREENSHOT_BOUNDS = "screenshot_screen_bounds";
-
-    /**
-     * Parcel key for the task id of the task that the screen shot was taken of, sent with messages
-     * of type [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type int.
-     * @hide
-     */
-    String PARCEL_KEY_SCREENSHOT_TASK_ID = "screenshot_task_id";
-
-    /**
-     * Parcel key for the visible insets of the image sent with messages of type
-     * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link android.graphics.Insets} in
-     * screen coordinates.
-     * @hide
-     */
-    String PARCEL_KEY_SCREENSHOT_INSETS = "screenshot_insets";
+    @IntDef({ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS,
+            ScreenshotSource.SCREENSHOT_KEY_CHORD,
+            ScreenshotSource.SCREENSHOT_KEY_OTHER,
+            ScreenshotSource.SCREENSHOT_OVERVIEW,
+            ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
+            ScreenshotSource.SCREENSHOT_OTHER})
+    @interface ScreenshotSource {
+        int SCREENSHOT_GLOBAL_ACTIONS = 0;
+        int SCREENSHOT_KEY_CHORD = 1;
+        int SCREENSHOT_KEY_OTHER = 2;
+        int SCREENSHOT_OVERVIEW = 3;
+        int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4;
+        int SCREENSHOT_OTHER = 5;
+    }
 
     /**
      * @hide
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 3eb0923..2c48925 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -50,6 +50,9 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * This is used in conjunction with
@@ -74,11 +77,13 @@
     private Injector mInjector;
 
     private MetricsLogger mMetricsLogger;
+    protected ExecutorService mExecutorService;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mInjector = createInjector();
+        mExecutorService = Executors.newSingleThreadExecutor();
 
         Intent intentReceived = getIntent();
         String className = intentReceived.getComponent().getClassName();
@@ -118,30 +123,9 @@
                 mInjector.getIPackageManager(), getContentResolver());
         if (newIntent != null) {
             newIntent.prepareToLeaveUser(callingUserId);
-
-            final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY,
-                    targetUserId);
-            try {
-                startActivityAsCaller(newIntent, null, null, false, targetUserId);
-            } catch (RuntimeException e) {
-                int launchedFromUid = -1;
-                String launchedFromPackage = "?";
-                try {
-                    launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid(
-                            getActivityToken());
-                    launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage(
-                            getActivityToken());
-                } catch (RemoteException ignored) {
-                }
-
-                Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package "
-                        + launchedFromPackage + ", while running in "
-                        + ActivityThread.currentProcessName(), e);
-            }
-
-            if (shouldShowDisclosure(ri, intentReceived)) {
-                mInjector.showToast(userMessageId, Toast.LENGTH_LONG);
-            }
+            maybeShowDisclosureAsync(intentReceived, newIntent, targetUserId, userMessageId);
+            CompletableFuture.runAsync(() -> startActivityAsCaller(
+                    newIntent, targetUserId), mExecutorService);
         } else {
             Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user "
                     + callingUserId + " to user " + targetUserId);
@@ -149,6 +133,44 @@
         finish();
     }
 
+    private void maybeShowDisclosureAsync(
+            Intent intentReceived, Intent newIntent, int userId, int messageId) {
+        final CompletableFuture<ResolveInfo> resolveInfoFuture =
+                mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, userId);
+        resolveInfoFuture.thenAcceptAsync(ri -> {
+            if (shouldShowDisclosure(ri, intentReceived)) {
+                mInjector.showToast(messageId, Toast.LENGTH_LONG);
+            }
+        }, getApplicationContext().getMainExecutor());
+    }
+
+    private void startActivityAsCaller(Intent newIntent, int userId) {
+        try {
+            startActivityAsCaller(
+                    newIntent,
+                    /* options= */ null,
+                    /* permissionToken= */ null,
+                    /* ignoreTargetSecurity= */ false,
+                    userId);
+        } catch (RuntimeException e) {
+            int launchedFromUid = -1;
+            String launchedFromPackage = "?";
+            try {
+                launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid(
+                        getActivityToken());
+                launchedFromPackage = ActivityTaskManager.getService()
+                        .getLaunchedFromPackage(getActivityToken());
+            } catch (RemoteException ignored) {
+            }
+
+            Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package "
+                    + launchedFromPackage + ", while running in "
+                    + ActivityThread.currentProcessName(), e);
+        } finally {
+            mExecutorService.shutdown();
+        }
+    }
+
     private void launchChooserActivityWithCorrectTab(Intent intentReceived, String className) {
         // When showing the sharesheet, instead of forwarding to the other profile,
         // we launch the sharesheet in the current user and select the other tab.
@@ -322,8 +344,11 @@
         }
 
         @Override
-        public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
-            return getPackageManager().resolveActivityAsUser(intent, flags, userId);
+        @Nullable
+        public CompletableFuture<ResolveInfo> resolveActivityAsUser(
+                Intent intent, int flags, int userId) {
+            return CompletableFuture.supplyAsync(
+                    () -> getPackageManager().resolveActivityAsUser(intent, flags, userId));
         }
 
         @Override
@@ -339,7 +364,7 @@
 
         PackageManager getPackageManager();
 
-        ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId);
+        CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId);
 
         void showToast(@StringRes int messageId, int duration);
     }
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index ebfea450..56a6db9 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -46,6 +46,9 @@
         r.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT),
                 false, this, UserHandle.USER_ALL);
+        r.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
+                false, this, UserHandle.USER_ALL);
     }
 
     public void unregister() {
@@ -68,6 +71,11 @@
         return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT);
     }
 
+    public boolean areNavigationButtonForcedVisible() {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0;
+    }
+
     private int getSensitivity(Resources userRes, String side) {
         final int inset = userRes.getDimensionPixelSize(
                 com.android.internal.R.dimen.config_backGestureInset);
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 7cff90b..adadc5e 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,5 +1,7 @@
 package com.android.internal.util;
 
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -10,11 +12,12 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
@@ -23,6 +26,109 @@
 import java.util.function.Consumer;
 
 public class ScreenshotHelper {
+
+    /**
+     * Describes a screenshot request (to make it easier to pass data through to the handler).
+     */
+    public static class ScreenshotRequest implements Parcelable {
+        private int mSource;
+        private boolean mHasStatusBar;
+        private boolean mHasNavBar;
+        private Bitmap mBitmap;
+        private Rect mBoundsInScreen;
+        private Insets mInsets;
+        private int mTaskId;
+
+        ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
+            mSource = source;
+            mHasStatusBar = hasStatus;
+            mHasNavBar = hasNav;
+        }
+
+        ScreenshotRequest(
+                int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) {
+            mSource = source;
+            mBitmap = bitmap;
+            mBoundsInScreen = boundsInScreen;
+            mInsets = insets;
+            mTaskId = taskId;
+        }
+
+        ScreenshotRequest(Parcel in) {
+            mSource = in.readInt();
+            mHasStatusBar = in.readBoolean();
+            mHasNavBar = in.readBoolean();
+            if (in.readInt() == 1) {
+                mBitmap = in.readParcelable(Bitmap.class.getClassLoader());
+                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader());
+                mInsets = in.readParcelable(Insets.class.getClassLoader());
+                mTaskId = in.readInt();
+            }
+        }
+
+        public int getSource() {
+            return mSource;
+        }
+
+        public boolean getHasStatusBar() {
+            return mHasStatusBar;
+        }
+
+        public boolean getHasNavBar() {
+            return mHasNavBar;
+        }
+
+        public Bitmap getBitmap() {
+            return mBitmap;
+        }
+
+        public Rect getBoundsInScreen() {
+            return mBoundsInScreen;
+        }
+
+        public Insets getInsets() {
+            return mInsets;
+        }
+
+        public int getTaskId() {
+            return mTaskId;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mSource);
+            dest.writeBoolean(mHasStatusBar);
+            dest.writeBoolean(mHasNavBar);
+            if (mBitmap == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                dest.writeParcelable(mBitmap, 0);
+                dest.writeParcelable(mBoundsInScreen, 0);
+                dest.writeParcelable(mInsets, 0);
+                dest.writeInt(mTaskId);
+            }
+        }
+
+        public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR =
+                new Parcelable.Creator<ScreenshotRequest>() {
+
+                    @Override
+                    public ScreenshotRequest createFromParcel(Parcel source) {
+                        return new ScreenshotRequest(source);
+                    }
+
+                    @Override
+                    public ScreenshotRequest[] newArray(int size) {
+                        return new ScreenshotRequest[size];
+                    }
+                };
+    }
     private static final String TAG = "ScreenshotHelper";
 
     // Time until we give up on the screenshot & show an error instead.
@@ -36,8 +142,10 @@
         mContext = context;
     }
 
+
+
     /**
-     * Request a screenshot be taken with a specific timeout.
+     * Request a screenshot be taken.
      *
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
@@ -47,6 +155,32 @@
      *                           or
      *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
      * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
+     *                           if not.
+     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
+     *                           false} if not.
+     * @param source             The source of the screenshot request. One of
+     *                           {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD,
+     *                           SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER}
+     * @param handler            A handler used in case the screenshot times out
+     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
+     *                           screenshot was taken.
+     */
+    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
+            final boolean hasNav, int source, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer) {
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
+        takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
+                completionConsumer);
+    }
+
+    /**
+     * Request a screenshot be taken, with provided reason.
+     *
+     * @param screenshotType     The type of screenshot, for example either
+     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
+     *                           or
+     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
+     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
      *                           if
      *                           not.
      * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
@@ -64,7 +198,7 @@
     }
 
     /**
-     * Request a screenshot be taken.
+     * Request a screenshot be taken with a specific timeout.
      *
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
@@ -89,9 +223,9 @@
     public void takeScreenshot(final int screenshotType, final boolean hasStatus,
             final boolean hasNav, long timeoutMs, @NonNull Handler handler,
             @Nullable Consumer<Uri> completionConsumer) {
-        takeScreenshot(screenshotType, hasStatus, hasNav, timeoutMs, handler, null,
-                completionConsumer
-        );
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
+                hasNav);
+        takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
     }
 
     /**
@@ -106,23 +240,16 @@
      *                           screenshot was taken.
      */
     public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen,
-            @NonNull Insets insets, int taskId, @NonNull Handler handler,
-            @Nullable Consumer<Uri> completionConsumer) {
-        Bundle imageBundle = new Bundle();
-        imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP, screenshot);
-        imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS, boundsInScreen);
-        imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_INSETS, insets);
-        imageBundle.putInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID, taskId);
-
-        takeScreenshot(
-                WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE,
-                false, false, // ignored when image bundle is set
-                SCREENSHOT_TIMEOUT_MS, handler, imageBundle, completionConsumer);
+            @NonNull Insets insets, int taskId, int source,
+            @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
+        ScreenshotRequest screenshotRequest =
+                new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId);
+        takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
+                handler, screenshotRequest, completionConsumer);
     }
 
-    private void takeScreenshot(final int screenshotType, final boolean hasStatus,
-            final boolean hasNav, long timeoutMs, @NonNull Handler handler,
-            @Nullable Bundle providedImage, @Nullable Consumer<Uri> completionConsumer) {
+    private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
+            ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
         synchronized (mScreenshotLock) {
             if (mScreenshotConnection != null) {
                 return;
@@ -157,7 +284,7 @@
                             return;
                         }
                         Messenger messenger = new Messenger(service);
-                        Message msg = Message.obtain(null, screenshotType);
+                        Message msg = Message.obtain(null, screenshotType, screenshotRequest);
                         final ServiceConnection myConn = this;
                         Handler h = new Handler(handler.getLooper()) {
                             @Override
@@ -175,12 +302,6 @@
                             }
                         };
                         msg.replyTo = new Messenger(h);
-                        msg.arg1 = hasStatus ? 1 : 0;
-                        msg.arg2 = hasNav ? 1 : 0;
-
-                        if (screenshotType == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
-                            msg.setData(providedImage);
-                        }
 
                         try {
                             messenger.send(msg);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 924dc4b..21985f0 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -119,6 +119,7 @@
 
 static pid_t gSystemServerPid = 0;
 
+static constexpr const char* kVoldAppDataIsolation = "persist.sys.vold_app_data_isolation_enabled";
 static constexpr const char* kPropFuse = "persist.sys.fuse";
 static const char kZygoteClassName[] = "com/android/internal/os/Zygote";
 static jclass gZygoteClass;
@@ -831,6 +832,7 @@
              multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
 
   bool isFuse = GetBoolProperty(kPropFuse, false);
+  bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
 
   if (isFuse) {
     if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
@@ -840,6 +842,9 @@
     } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
       const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id);
       BindMount(installer_source, "/storage", fail_fn);
+    } else if (isAppDataIsolationEnabled && mount_mode == MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+      const std::string writable_source = StringPrintf("/mnt/androidwritable/%d", user_id);
+      BindMount(writable_source, "/storage", fail_fn);
     } else {
       BindMount(user_source, "/storage", fail_fn);
     }
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 5890bed..25615d2 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -43,6 +43,7 @@
             android:fontFamily="@string/config_headlineFontFamilyMedium"
             android:textColor="@color/resolver_empty_state_text"
             android:textSize="14sp"
+            android:gravity="center_horizontal"
             android:layout_centerHorizontal="true" />
         <TextView
             android:id="@+id/resolver_empty_state_subtitle"
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 5424b6f..43590ba 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -68,6 +68,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class IntentForwarderActivityTest {
@@ -633,6 +635,11 @@
         public void onCreate(@Nullable Bundle savedInstanceState) {
             getIntent().setComponent(sComponentName);
             super.onCreate(savedInstanceState);
+            try {
+                mExecutorService.awaitTermination(/* timeout= */ 30, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
         }
 
         @Override
@@ -671,7 +678,8 @@
         }
 
         @Override
-        public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
+        public CompletableFuture<ResolveInfo> resolveActivityAsUser(
+                Intent intent, int flags, int userId) {
             ActivityInfo activityInfo = new ActivityInfo();
             activityInfo.packageName = sPackageName;
             activityInfo.name = sActivityName;
@@ -680,7 +688,7 @@
             ResolveInfo resolveInfo = new ResolveInfo();
             resolveInfo.activityInfo = activityInfo;
 
-            return resolveInfo;
+            return CompletableFuture.completedFuture(resolveInfo);
         }
 
         @Override
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index cd6b3af..fe33cd8 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -36,6 +36,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
+import android.view.WindowManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -91,7 +92,8 @@
     public void testProvidedImageScreenshot() {
         mScreenshotHelper.provideScreenshot(
                 Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(),
-                Insets.of(0, 0, 0, 0), 1, mHandler, null);
+                Insets.of(0, 0, 0, 0), 1,
+                WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
     }
 
     @Test
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index cd878c5..228d03a 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -120,7 +120,7 @@
 
     /**
      * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
-     *
+     * <p>
      * In single buffered mode the application is responsible for serializing access to the image
      * content buffer. Each time the image content is to be updated, the
      * {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -143,7 +143,7 @@
 
     /**
      * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
-     *
+     * <p>
      * In single buffered mode the application is responsible for serializing access to the image
      * content buffer. Each time the image content is to be updated, the
      * {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -152,7 +152,7 @@
      * must be called before each ANativeWindow_lock, or that call will fail. When producing
      * image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
      * OpenGL ES function call each frame.
-     *
+     * <p>
      * Unlike {@link #SurfaceTexture(int, boolean)}, which takes an OpenGL texture object name,
      * this constructor creates the SurfaceTexture in detached mode. A texture name must be passed
      * in using {@link #attachToGLContext} before calling {@link #releaseTexImage()} and producing
@@ -222,15 +222,15 @@
      * method.  Both video and camera based image producers do override the size.  This method may
      * be used to set the image size when producing images with {@link android.graphics.Canvas} (via
      * {@link android.view.Surface#lockCanvas}), or OpenGL ES (via an EGLSurface).
-     *
+     * <p>
      * The new default buffer size will take effect the next time the image producer requests a
      * buffer to fill.  For {@link android.graphics.Canvas} this will be the next time {@link
      * android.view.Surface#lockCanvas} is called.  For OpenGL ES, the EGLSurface should be
      * destroyed (via eglDestroySurface), made not-current (via eglMakeCurrent), and then recreated
-     * (via eglCreateWindowSurface) to ensure that the new default size has taken effect.
-     *
+     * (via {@code eglCreateWindowSurface}) to ensure that the new default size has taken effect.
+     * <p>
      * The width and height parameters must be no greater than the minimum of
-     * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see
+     * {@code GL_MAX_VIEWPORT_DIMS} and {@code GL_MAX_TEXTURE_SIZE} (see
      * {@link javax.microedition.khronos.opengles.GL10#glGetIntegerv glGetIntegerv}).
      * An error due to invalid dimensions might not be reported until
      * updateTexImage() is called.
@@ -242,7 +242,7 @@
     /**
      * Update the texture image to the most recent frame from the image stream.  This may only be
      * called while the OpenGL ES context that owns the texture is current on the calling thread.
-     * It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
+     * It will implicitly bind its texture to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
      */
     public void updateTexImage() {
         nativeUpdateTexImage();
@@ -251,6 +251,7 @@
     /**
      * Releases the the texture content. This is needed in single buffered mode to allow the image
      * content producer to take ownership of the image buffer.
+     * <p>
      * For more information see {@link #SurfaceTexture(int, boolean)}.
      */
     public void releaseTexImage() {
@@ -263,7 +264,7 @@
      * ES texture object will be deleted as a result of this call.  After calling this method all
      * calls to {@link #updateTexImage} will throw an {@link java.lang.IllegalStateException} until
      * a successful call to {@link #attachToGLContext} is made.
-     *
+     * <p>
      * This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
      * contexts.  Note, however, that the image contents are only accessible from one OpenGL ES
      * context at a time.
@@ -279,8 +280,8 @@
      * Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread.  A
      * new OpenGL ES texture object is created and populated with the SurfaceTexture image frame
      * that was current at the time of the last call to {@link #detachFromGLContext}.  This new
-     * texture is bound to the GL_TEXTURE_EXTERNAL_OES texture target.
-     *
+     * texture is bound to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
+     * <p>
      * This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
      * contexts.  Note, however, that the image contents are only accessible from one OpenGL ES
      * context at a time.
@@ -297,16 +298,16 @@
 
     /**
      * Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set by
-     * the most recent call to updateTexImage.
-     *
+     * the most recent call to {@link #updateTexImage}.
+     * <p>
      * This transform matrix maps 2D homogeneous texture coordinates of the form (s, t, 0, 1) with s
      * and t in the inclusive range [0, 1] to the texture coordinate that should be used to sample
      * that location from the texture.  Sampling the texture outside of the range of this transform
      * is undefined.
-     *
+     * <p>
      * The matrix is stored in column-major order so that it may be passed directly to OpenGL ES via
-     * the glLoadMatrixf or glUniformMatrix4fv functions.
-     *
+     * the {@code glLoadMatrixf} or {@code glUniformMatrix4fv} functions.
+     * <p>
      * If the underlying buffer has a crop associated with it, the transformation will also include
      * a slight scale to cut off a 1-texel border around the edge of the crop. This ensures that
      * when the texture is bilinear sampled that no texels outside of the buffer's valid region
@@ -326,7 +327,7 @@
 
     /**
      * Retrieve the timestamp associated with the texture image set by the most recent call to
-     * updateTexImage.
+     * {@link #updateTexImage}.
      *
      * <p>This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp
      * should be unaffected by time-of-day adjustments. The specific meaning and zero point of the
@@ -337,8 +338,8 @@
      *
      * <p>For camera sources, timestamps should be strictly monotonic. Timestamps from MediaPlayer
      * sources may be reset when the playback position is set. For EGL and Vulkan producers, the
-     * timestamp is the desired present time set with the EGL_ANDROID_presentation_time or
-     * VK_GOOGLE_display_timing extensions.</p>
+     * timestamp is the desired present time set with the {@code EGL_ANDROID_presentation_time} or
+     * {@code VK_GOOGLE_display_timing} extensions.</p>
      */
 
     public long getTimestamp() {
@@ -346,16 +347,17 @@
     }
 
     /**
-     * release() frees all the buffers and puts the SurfaceTexture into the
+     * {@code release()} frees all the buffers and puts the SurfaceTexture into the
      * 'abandoned' state. Once put in this state the SurfaceTexture can never
      * leave it. When in the 'abandoned' state, all methods of the
-     * IGraphicBufferProducer interface will fail with the NO_INIT error.
-     *
+     * {@code IGraphicBufferProducer} interface will fail with the {@code NO_INIT}
+     * error.
+     * <p>
      * Note that while calling this method causes all the buffers to be freed
      * from the perspective of the the SurfaceTexture, if there are additional
      * references on the buffers (e.g. if a buffer is referenced by a client or
      * by OpenGL ES as a texture) then those buffer will remain allocated.
-     *
+     * <p>
      * Always call this method when you are done with SurfaceTexture. Failing
      * to do so may delay resource deallocation for a significant amount of
      * time.
@@ -367,7 +369,7 @@
     }
 
     /**
-     * Returns true if the SurfaceTexture was released.
+     * Returns {@code true} if the SurfaceTexture was released.
      *
      * @see #release()
      */
@@ -400,7 +402,7 @@
     }
 
     /**
-     * Returns true if the SurfaceTexture is single-buffered
+     * Returns {@code true} if the SurfaceTexture is single-buffered.
      * @hide
      */
     public boolean isSingleBuffered() {
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index 14249cb..8e8dfaf 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -78,7 +78,8 @@
                                     listeners.add((AudioManager.OnAudioPortUpdateListener)msg.obj);
                                 }
                             } else {
-                                listeners = mListeners;
+                                listeners = (ArrayList<AudioManager.OnAudioPortUpdateListener>)
+                                        mListeners.clone();
                             }
                         }
                         // reset audio port cache if the event corresponds to a change coming
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index dc06153..a8b82ba 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -24,11 +24,15 @@
  * @hide
  */
 oneway interface IMediaRouter2 {
-    void notifyRestoreRoute();
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
     void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
     void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
     void notifySessionReleased(in RoutingSessionInfo sessionInfo);
+    /**
+     * Gets hints of the new session for the given route.
+     * Call MediaRouterService#notifySessionHintsForCreatingSession to pass the result.
+     */
+    void getSessionHintsForCreatingSession(long uniqueRequestId, in MediaRoute2Info route);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 0d87736..52bac67 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -59,6 +59,8 @@
 
     void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
             in MediaRoute2Info route, in @nullable Bundle sessionHints);
+    void notifySessionHintsForCreatingSession(IMediaRouter2 router, long uniqueRequestId,
+                in MediaRoute2Info route, in @nullable Bundle sessionHints);
     void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 97d32d8..c652628 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -388,7 +388,7 @@
             @Override
             public void onReclaimResources() {
                 synchronized (mSessionMap) {
-                    mSessionMap.forEach((casSession, sessionResourceId) -> casSession.close());
+                    mSessionMap.forEach((casSession, sessionResourceHandle) -> casSession.close());
                 }
                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
                         EventHandler.MSG_CAS_RESOURCE_LOST));
@@ -868,7 +868,7 @@
         }
     }
 
-    private int getSessionResourceId() throws MediaCasException {
+    private int getSessionResourceHandle() throws MediaCasException {
         validateInternalStates();
 
         int[] sessionResourceHandle = new int[1];
@@ -881,14 +881,14 @@
                     "insufficient resource to Open Session");
             }
         }
-        return  sessionResourceHandle[0];
+        return sessionResourceHandle[0];
     }
 
-    private void addSessionToResourceMap(Session session, int sessionResourceId) {
+    private void addSessionToResourceMap(Session session, int sessionResourceHandle) {
 
-        if (sessionResourceId != -1) {
+        if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
             synchronized (mSessionMap) {
-                mSessionMap.put(session, sessionResourceId);
+                mSessionMap.put(session, sessionResourceHandle);
             }
         }
     }
@@ -918,13 +918,13 @@
      * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public Session openSession() throws MediaCasException {
-        int sessionResourceId = getSessionResourceId();
+        int sessionResourceHandle = getSessionResourceHandle();
 
         try {
             OpenSessionCallback cb = new OpenSessionCallback();
             mICas.openSession(cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
-            addSessionToResourceMap(cb.mSession, sessionResourceId);
+            addSessionToResourceMap(cb.mSession, sessionResourceHandle);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
@@ -952,7 +952,7 @@
     @Nullable
     public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
             throws MediaCasException {
-        int sessionResourceId = getSessionResourceId();
+        int sessionResourceHandle = getSessionResourceHandle();
 
         if (mICasV12 == null) {
             Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface");
@@ -963,7 +963,7 @@
             OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback();
             mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
-            addSessionToResourceMap(cb.mSession, sessionResourceId);
+            addSessionToResourceMap(cb.mSession, sessionResourceHandle);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index bd8fb96..0ea9624 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -237,9 +237,9 @@
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to unregister media router.", ex);
                 }
+                mStub = null;
             }
             mShouldUpdateRoutes = true;
-            mStub = null;
         }
     }
 
@@ -690,6 +690,31 @@
         matchingController.releaseInternal(/* shouldReleaseSession= */ false);
     }
 
+    void onGetControllerHintsForCreatingSessionOnHandler(long uniqueRequestId,
+            MediaRoute2Info route) {
+        OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
+        Bundle controllerHints = null;
+        if (listener != null) {
+            controllerHints = listener.onGetControllerHints(route);
+            if (controllerHints != null) {
+                controllerHints = new Bundle(controllerHints);
+            }
+        }
+
+        MediaRouter2Stub stub;
+        synchronized (sRouterLock) {
+            stub = mStub;
+        }
+        if (stub != null) {
+            try {
+                mMediaRouterService.notifySessionHintsForCreatingSession(
+                        stub, uniqueRequestId, route, controllerHints);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "getSessionHintsOnHandler: Unable to request.", ex);
+            }
+        }
+    }
+
     private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
             RouteDiscoveryPreference discoveryRequest) {
         return routes.stream()
@@ -820,13 +845,14 @@
      */
     public interface OnGetControllerHintsListener {
         /**
-         * Called when the {@link MediaRouter2} is about to request
-         * the media route provider service to create a controller with the given route.
+         * Called when the {@link MediaRouter2} or the system is about to request
+         * a media route provider service to create a controller with the given route.
          * The {@link Bundle} returned here will be sent to media route provider service as a hint.
          * <p>
-         * To send hints when creating the controller, set the listener before calling
-         * {@link #transferTo(MediaRoute2Info)}. The method will be called
-         * on the same thread which calls {@link #transferTo(MediaRoute2Info)}.
+         * Since controller creation can be requested by the {@link MediaRouter2} and the system,
+         * set the listener as soon as possible after acquiring {@link MediaRouter2} instance.
+         * The method will be called on the same thread that calls
+         * {@link #transferTo(MediaRoute2Info)} or the main thread if it is requested by the system.
          *
          * @param route The route to create controller with
          * @return An optional bundle of app-specific arguments to send to the provider,
@@ -1378,9 +1404,6 @@
 
     class MediaRouter2Stub extends IMediaRouter2.Stub {
         @Override
-        public void notifyRestoreRoute() throws RemoteException {}
-
-        @Override
         public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
             mHandler.sendMessage(obtainMessage(MediaRouter2::addRoutesOnHandler,
                     MediaRouter2.this, routes));
@@ -1415,5 +1438,13 @@
             mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
                     MediaRouter2.this, sessionInfo));
         }
+
+        @Override
+        public void getSessionHintsForCreatingSession(long uniqueRequestId,
+                @NonNull MediaRoute2Info route) {
+            mHandler.sendMessage(obtainMessage(
+                    MediaRouter2::onGetControllerHintsForCreatingSessionOnHandler,
+                    MediaRouter2.this, uniqueRequestId, route));
+        }
     }
 }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index b694fd0..3b570b6 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -147,14 +147,16 @@
         }
 
         synchronized (sLock) {
-            if (mCallbackRecords.size() == 0 && mClient != null) {
-                try {
-                    mMediaRouterService.unregisterManager(mClient);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to unregister media router manager", ex);
+            if (mCallbackRecords.size() == 0) {
+                if (mClient != null) {
+                    try {
+                        mMediaRouterService.unregisterManager(mClient);
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "Unable to unregister media router manager", ex);
+                    }
+                    mClient = null;
                 }
-                //TODO: clear mRoutes?
-                mClient = null;
+                mRoutes.clear();
                 mPreferredFeaturesMap.clear();
             }
         }
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
index adf4d3d..022cfee 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
@@ -80,7 +80,8 @@
                                         (AudioManager.VolumeGroupCallback) msg.obj);
                             }
                         } else {
-                            listeners = mListeners;
+                            listeners = (ArrayList<AudioManager.VolumeGroupCallback>)
+                                    mListeners.clone();
                         }
                     }
                     if (listeners.isEmpty()) {
diff --git a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 7077cd1..487b444 100644
--- a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -218,11 +218,11 @@
      * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this request.
      *
      * @param request {@link CasSessionRequest} information of the current request.
-     * @param sessionResourceId a one-element array to return the granted cas session id.
+     * @param casSessionHandle a one-element array to return the granted cas session handle.
      *
      * @return true if there is CAS session granted.
      */
-    boolean requestCasSession(in CasSessionRequest request, out int[] sessionResourceId);
+    boolean requestCasSession(in CasSessionRequest request, out int[] casSessionHandle);
 
     /*
      * This API is used by the Tuner framework to request an available Lnb from the TunerHAL.
@@ -276,7 +276,7 @@
      *
      * <p>Client must call this whenever it releases a descrambler.
      *
-     * @param demuxHandle the handle of the released Tuner Descrambler.
+     * @param descramblerHandle the handle of the released Tuner Descrambler.
      * @param clientId the id of the client that is releasing the descrambler.
      */
     void releaseDescrambler(in int descramblerHandle, int clientId);
@@ -288,10 +288,10 @@
      *
      * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release.
      *
-     * @param sessionResourceId the id of the released CAS session.
+     * @param casSessionHandle the handle of the released CAS session.
      * @param clientId the id of the client that is releasing the cas session.
      */
-    void releaseCasSession(in int sessionResourceId, int clientId);
+    void releaseCasSession(in int casSessionHandle, int clientId);
 
     /*
      * Notifies the TRM that the Lnb with the given handle was released.
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index b4dcc5d..be102d8 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -362,17 +362,16 @@
      * request.
      *
      * @param request {@link CasSessionRequest} information of the current request.
-     * @param sessionResourceId a one-element array to return the granted cas session id.
-     *                          If no CAS granted, this will return
-     *                          {@link #INVALID_CAS_SESSION_RESOURCE_ID}.
+     * @param casSessionHandle a one-element array to return the granted cas session handel.
+     *                         If no CAS granted, this will return {@link #INVALID_RESOURCE_HANDLE}.
      *
      * @return true if there is CAS session granted.
      */
     public boolean requestCasSession(@NonNull CasSessionRequest request,
-                @NonNull int[] sessionResourceId) {
+                @NonNull int[] casSessionHandle) {
         boolean result = false;
         try {
-            result = mService.requestCasSession(request, sessionResourceId);
+            result = mService.requestCasSession(request, casSessionHandle);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -471,12 +470,12 @@
      * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
      * release.
      *
-     * @param sessionResourceId the id of the released CAS session.
+     * @param casSessionHandle the handle of the released CAS session.
      * @param clientId the id of the client that is releasing the cas session.
      */
-    public void releaseCasSession(int sessionResourceId, int clientId) {
+    public void releaseCasSession(int casSessionHandle, int clientId) {
         try {
-            mService.releaseCasSession(sessionResourceId, clientId);
+            mService.releaseCasSession(casSessionHandle, clientId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 6ca564f..6a1e965 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -36,6 +36,7 @@
 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 android.content.Context;
@@ -47,6 +48,7 @@
 import android.media.MediaRouter2Utils;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
+import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -73,6 +75,8 @@
     private static final String TAG = "MediaRouter2ManagerTest";
     private static final int WAIT_TIME_MS = 2000;
     private static final int TIMEOUT_MS = 5000;
+    private static final String TEST_KEY = "test_key";
+    private static final String TEST_VALUE = "test_value";
 
     private Context mContext;
     private MediaRouter2Manager mManager;
@@ -160,6 +164,7 @@
         });
 
         MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
+        assertNotNull(routeToRemove);
 
         StubMediaRoute2ProviderService sInstance =
                 StubMediaRoute2ProviderService.getInstance();
@@ -171,6 +176,52 @@
         assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
+    @Test
+    public void testGetRoutes_removedRoute_returnsCorrectRoutes() throws Exception {
+        CountDownLatch addedLatch = new CountDownLatch(1);
+        CountDownLatch removedLatch = new CountDownLatch(1);
+
+        RouteCallback routeCallback = new RouteCallback() {
+            // Used to ensure the removed route is added.
+            @Override
+            public void onRoutesAdded(List<MediaRoute2Info> routes) {
+                if (removedLatch.getCount() > 0) {
+                    return;
+                }
+                addedLatch.countDown();
+            }
+
+            @Override
+            public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+                removedLatch.countDown();
+            }
+        };
+
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+        mRouteCallbacks.add(routeCallback);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
+        assertNotNull(routeToRemove);
+
+        StubMediaRoute2ProviderService sInstance =
+                StubMediaRoute2ProviderService.getInstance();
+        assertNotNull(sInstance);
+        sInstance.removeRoute(ROUTE_ID2);
+
+        // Wait until the route is removed.
+        assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        Map<String, MediaRoute2Info> newRoutes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        assertNull(newRoutes.get(ROUTE_ID2));
+
+        // Revert the removal.
+        sInstance.addRoute(routeToRemove);
+        assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        mRouter2.unregisterRouteCallback(routeCallback);
+    }
+
     /**
      * Tests if we get proper routes for application that has special route feature.
      */
@@ -465,6 +516,56 @@
         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
     }
 
+    @Test
+    public void testRouter2SetOnGetControllerHintsListener() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        addRouterCallback(new RouteCallback() {});
+
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final Bundle controllerHints = new Bundle();
+        controllerHints.putString(TEST_KEY, TEST_VALUE);
+        final CountDownLatch hintLatch = new CountDownLatch(1);
+        final MediaRouter2.OnGetControllerHintsListener listener =
+                route1 -> {
+                    hintLatch.countDown();
+                    return controllerHints;
+                };
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+
+        addManagerCallback(new MediaRouter2Manager.Callback() {
+            @Override
+            public void onTransferred(RoutingSessionInfo oldSession,
+                    RoutingSessionInfo newSession) {
+                assertTrue(newSession.getSelectedRoutes().contains(route.getId()));
+                // The StubMediaRoute2ProviderService is supposed to set control hints
+                // with the given controllerHints.
+                Bundle controlHints = newSession.getControlHints();
+                assertNotNull(controlHints);
+                assertTrue(controlHints.containsKey(TEST_KEY));
+                assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY));
+
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailed(RoutingSessionInfo session,
+                    MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        });
+
+        mRouter2.setOnGetControllerHintsListener(listener);
+        mManager.selectRoute(mPackageName, route);
+        assertTrue(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
     Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
             throws Exception {
         CountDownLatch addedLatch = new CountDownLatch(1);
@@ -475,8 +576,8 @@
         MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
             @Override
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                for (int i = 0; i < routes.size(); i++) {
-                    if (!routes.get(i).isSystemRoute()) {
+                for (MediaRoute2Info route : routes) {
+                    if (!route.isSystemRoute()) {
                         addedLatch.countDown();
                         break;
                     }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index 6d46ba5..4e398f2 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -65,9 +65,9 @@
     public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
 
     public static final String FEATURE_SAMPLE =
-            "com.android.mediarouteprovider.FEATURE_SAMPLE";
+            "com.android.mediaroutertest.FEATURE_SAMPLE";
     public static final String FEATURE_SPECIAL =
-            "com.android.mediarouteprovider.FEATURE_SPECIAL";
+            "com.android.mediaroutertest..FEATURE_SPECIAL";
 
     Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
     Map<String, String> mRouteIdToSessionId = new HashMap<>();
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
index 7004fb6..8b235e6 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -29,8 +29,6 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    androidprv:layout_maxWidth="@dimen/keyguard_security_width"
-    androidprv:layout_maxHeight="@dimen/keyguard_security_height"
     android:gravity="center">
 
     <include layout="@layout/keyguard_message_area" />
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index e6fb501..a5445c5 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -86,25 +86,25 @@
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
-        <item>com.android.systemui.recents.Recents</item>
+<!--        <item>com.android.systemui.recents.Recents</item>-->
         <item>com.android.systemui.volume.VolumeUI</item>
-        <item>com.android.systemui.stackdivider.Divider</item>
+<!--        <item>com.android.systemui.stackdivider.Divider</item>-->
 <!--        <item>com.android.systemui.statusbar.phone.StatusBar</item>-->
         <item>com.android.systemui.usb.StorageNotification</item>
         <item>com.android.systemui.power.PowerUI</item>
         <item>com.android.systemui.media.RingtonePlayer</item>
-        <item>com.android.systemui.keyboard.KeyboardUI</item>
-        <item>com.android.systemui.pip.PipUI</item>
-        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
+<!--        <item>com.android.systemui.keyboard.KeyboardUI</item>-->
+<!--        <item>com.android.systemui.pip.PipUI</item>-->
+<!--        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>-->
         <item>@string/config_systemUIVendorServiceComponent</item>
         <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
-        <item>com.android.systemui.LatencyTester</item>
-        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
+<!--        <item>com.android.systemui.LatencyTester</item>-->
+<!--        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>-->
         <item>com.android.systemui.ScreenDecorations</item>
         <item>com.android.systemui.biometrics.AuthController</item>
-        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
+<!--        <item>com.android.systemui.SliceBroadcastRelayHandler</item>-->
         <item>com.android.systemui.SizeCompatModeActivityController</item>
-        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
+<!--        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>-->
         <item>com.android.systemui.theme.ThemeOverlayController</item>
         <item>com.android.systemui.navigationbar.car.CarNavigationBar</item>
         <item>com.android.systemui.toast.ToastUI</item>
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 60ee19e..4fde309 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -205,6 +205,9 @@
 
     @Override
     public void onCancelClicked() {
+        getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
+        getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false);
+
         mBouncer.hide(/* destroyView= */ true);
         mKeyguardCancelClickedListener.onCancelClicked();
     }
@@ -226,7 +229,8 @@
 
     @Override
     public void setNeedsInput(boolean needsInput) {
-        getLayout().setFocusable(needsInput);
+        getOverlayViewGlobalStateController().setWindowFocusable(needsInput);
+        getOverlayViewGlobalStateController().setWindowNeedsInput(needsInput);
     }
 
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 9fdfc0f..2c2aec2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -52,7 +52,6 @@
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
@@ -80,7 +79,6 @@
     private final Handler mMainHandler;
     private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
-    private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private final ButtonSelectionStateController mButtonSelectionStateController;
     private final PhoneStatusBarPolicy mIconPolicy;
     private final StatusBarIconController mIconController;
@@ -127,7 +125,6 @@
             @Main Handler mainHandler,
             Lazy<KeyguardStateController> keyguardStateControllerLazy,
             Lazy<NavigationBarController> navigationBarControllerLazy,
-            SuperStatusBarViewFactory superStatusBarViewFactory,
             ButtonSelectionStateController buttonSelectionStateController,
             PhoneStatusBarPolicy iconPolicy,
             StatusBarIconController iconController
@@ -143,7 +140,6 @@
         mMainHandler = mainHandler;
         mKeyguardStateControllerLazy = keyguardStateControllerLazy;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
-        mSuperStatusBarViewFactory = superStatusBarViewFactory;
         mButtonSelectionStateController = buttonSelectionStateController;
         mIconPolicy = iconPolicy;
         mIconController = iconController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
index 402d742..5fe03f1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
@@ -123,6 +123,12 @@
         mSystemUIOverlayWindowController.setWindowFocusable(focusable);
     }
 
+    /** Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
+     * sysui overlay window */
+    public void setWindowNeedsInput(boolean needsInput) {
+        mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput);
+    }
+
     /** Returns {@code true} if the window is focusable. */
     public boolean isWindowFocusable() {
         return mSystemUIOverlayWindowController.isWindowFocusable();
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
index 04d69ea..5df5d6e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
@@ -132,6 +132,16 @@
         updateWindow();
     }
 
+    /** Sets the window to enable IME. */
+    public void setWindowNeedsInput(boolean needsInput) {
+        if (needsInput) {
+            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        } else {
+            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        }
+        updateWindow();
+    }
+
     /** Returns {@code true} if the window is visible */
     public boolean isWindowVisible() {
         return mVisible;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
index 3ecb29f..6da34d4 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
@@ -38,7 +38,6 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -75,8 +74,6 @@
     @Mock
     private NavigationBarController mNavigationBarController;
     @Mock
-    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
-    @Mock
     private ButtonSelectionStateController mButtonSelectionStateController;
     @Mock
     private PhoneStatusBarPolicy mIconPolicy;
@@ -92,8 +89,7 @@
                 mCarNavigationBarController, mWindowManager, mDeviceProvisionedController,
                 new CommandQueue(mContext), mAutoHideController, mButtonSelectionStateListener,
                 mHandler, () -> mKeyguardStateController, () -> mNavigationBarController,
-                mSuperStatusBarViewFactory, mButtonSelectionStateController, mIconPolicy,
-                mIconController);
+                mButtonSelectionStateController, mIconPolicy, mIconController);
     }
 
     @Test
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index 54986a4..8aac1c9 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -120,6 +120,7 @@
     },
     manifest: "shim/AndroidManifestTargetPSdk.xml",
     apex_available: [
+        "//apex_available:platform",
         "com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p",
     ],
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index e551b69..ee8fb38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -38,7 +38,7 @@
 
     BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
             MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
-        super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE, routerManager, info, packageName);
+        super(context, routerManager, info, packageName);
         mCachedDevice = device;
         initDeviceRecord();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 85fa988..83a9671 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -38,7 +38,7 @@
 
     InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
             String packageName) {
-        super(context, MediaDeviceType.TYPE_CAST_DEVICE, routerManager, info, packageName);
+        super(context, routerManager, info, packageName);
         initDeviceRecord();
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 39e6a12..6aff301 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -15,6 +15,16 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
+import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -38,13 +48,21 @@
     private static final String TAG = "MediaDevice";
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({MediaDeviceType.TYPE_CAST_DEVICE,
+    @IntDef({MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE,
+            MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE,
+            MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE,
             MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
+            MediaDeviceType.TYPE_CAST_DEVICE,
+            MediaDeviceType.TYPE_CAST_GROUP_DEVICE,
             MediaDeviceType.TYPE_PHONE_DEVICE})
     public @interface MediaDeviceType {
-        int TYPE_PHONE_DEVICE = 1;
-        int TYPE_CAST_DEVICE = 2;
-        int TYPE_BLUETOOTH_DEVICE = 3;
+        int TYPE_USB_C_AUDIO_DEVICE = 1;
+        int TYPE_3POINT5_MM_AUDIO_DEVICE = 2;
+        int TYPE_FAST_PAIR_BLUETOOTH_DEVICE = 3;
+        int TYPE_BLUETOOTH_DEVICE = 4;
+        int TYPE_CAST_DEVICE = 5;
+        int TYPE_CAST_GROUP_DEVICE = 6;
+        int TYPE_PHONE_DEVICE = 7;
     }
 
     @VisibleForTesting
@@ -58,13 +76,43 @@
     protected final MediaRouter2Manager mRouterManager;
     protected final String mPackageName;
 
-    MediaDevice(Context context, @MediaDeviceType int type, MediaRouter2Manager routerManager,
-            MediaRoute2Info info, String packageName) {
-        mType = type;
+    MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+            String packageName) {
         mContext = context;
         mRouteInfo = info;
         mRouterManager = routerManager;
         mPackageName = packageName;
+        setType(info);
+    }
+
+    private void setType(MediaRoute2Info info) {
+        if (info == null) {
+            mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+            return;
+        }
+
+        switch (info.getType()) {
+            case TYPE_GROUP:
+                mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
+                break;
+            case TYPE_BUILTIN_SPEAKER:
+                mType = MediaDeviceType.TYPE_PHONE_DEVICE;
+                break;
+            case TYPE_WIRED_HEADSET:
+            case TYPE_WIRED_HEADPHONES:
+                mType = MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE;
+                break;
+            case TYPE_HEARING_AID:
+            case TYPE_BLUETOOTH_A2DP:
+                mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+                break;
+            case TYPE_UNKNOWN:
+            case TYPE_REMOTE_TV:
+            case TYPE_REMOTE_SPEAKER:
+            default:
+                mType = MediaDeviceType.TYPE_CAST_DEVICE;
+                break;
+        }
     }
 
     void initDeviceRecord() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index af88723..c6c5ade 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -42,7 +42,7 @@
 
     PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
             String packageName) {
-        super(context, MediaDeviceType.TYPE_PHONE_DEVICE, routerManager, info, packageName);
+        super(context, routerManager, info, packageName);
 
         initDeviceRecord();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 653c8ad..deccde5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -33,6 +33,7 @@
 import android.util.Slog;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -395,8 +396,12 @@
             button1.setAlpha(button1.isEnabled() ? 1f : .5f);
             button2.setAlpha(button2.isEnabled() ? 1f : .5f);
         } else {
-            button1.setVisibility(View.GONE);
-            button2.setVisibility(View.GONE);
+            if (button1 != null) {
+                ((ViewGroup) row).removeView(button1);
+            }
+            if (button2 != null) {
+                ((ViewGroup) row).removeView(button2);
+            }
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index c713d78..d7e76a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -133,6 +133,35 @@
         }
     }
 
+    /**
+     * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received.
+     * This replaces the dependency on the initial sticky broadcast.
+     */
+    public void fetchInitialState() {
+        if (mWifiManager == null) {
+            return;
+        }
+        updateWifiState();
+        final NetworkInfo networkInfo =
+                mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+        connected = networkInfo != null && networkInfo.isConnected();
+        mWifiInfo = null;
+        ssid = null;
+        if (connected) {
+            mWifiInfo = mWifiManager.getConnectionInfo();
+            if (mWifiInfo != null) {
+                if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) {
+                    ssid = mWifiInfo.getPasspointProviderFriendlyName();
+                } else {
+                    ssid = getValidSsid(mWifiInfo);
+                }
+                updateRssi(mWifiInfo.getRssi());
+                maybeRequestNetworkScore();
+            }
+        }
+        updateStatusLabel();
+    }
+
     public void handleBroadcast(Intent intent) {
         if (mWifiManager == null) {
             return;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 4b08387..db05b76 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -144,12 +148,19 @@
         when(mCachedDevice2.isConnected()).thenReturn(true);
         when(mCachedDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(mCachedDevice3.isConnected()).thenReturn(true);
+        when(mBluetoothRouteInfo1.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
+        when(mBluetoothRouteInfo2.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
+        when(mBluetoothRouteInfo3.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
         when(mRouteInfo1.getId()).thenReturn(ROUTER_ID_1);
         when(mRouteInfo2.getId()).thenReturn(ROUTER_ID_2);
         when(mRouteInfo3.getId()).thenReturn(ROUTER_ID_3);
         when(mRouteInfo1.getName()).thenReturn(DEVICE_NAME_1);
         when(mRouteInfo2.getName()).thenReturn(DEVICE_NAME_2);
         when(mRouteInfo3.getName()).thenReturn(DEVICE_NAME_3);
+        when(mRouteInfo1.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+        when(mRouteInfo2.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+        when(mRouteInfo3.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+        when(mPhoneRouteInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
         when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
         when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
@@ -271,12 +282,12 @@
 
     @Test
     public void compareTo_info_bluetooth_infoFirst() {
-        mMediaDevices.add(mBluetoothMediaDevice1);
         mMediaDevices.add(mInfoMediaDevice1);
+        mMediaDevices.add(mBluetoothMediaDevice1);
 
-        assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
-        Collections.sort(mMediaDevices, COMPARATOR);
         assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice1);
+        Collections.sort(mMediaDevices, COMPARATOR);
+        assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
     }
 
     @Test
@@ -327,7 +338,7 @@
     // 5.mBluetoothMediaDevice2: * 2 times usage
     // 6.mBluetoothMediaDevice3: * 1 time usage
     // 7.mPhoneMediaDevice:      * 0 time usage
-    // Order: 7 -> 2 -> 1 -> 3 -> 5 -> 4 -> 6
+    // Order: 7 -> 2 -> 1 -> 5 -> 3 -> 6 -> 4
     @Test
     public void compareTo_mixedDevices_carKitFirst() {
         when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -352,10 +363,10 @@
         assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
         assertThat(mMediaDevices.get(1)).isEqualTo(mBluetoothMediaDevice1);
         assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice1);
-        assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice2);
-        assertThat(mMediaDevices.get(4)).isEqualTo(mBluetoothMediaDevice2);
-        assertThat(mMediaDevices.get(5)).isEqualTo(mInfoMediaDevice3);
-        assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice3);
+        assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice2);
+        assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice2);
+        assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice3);
+        assertThat(mMediaDevices.get(6)).isEqualTo(mInfoMediaDevice3);
     }
 
     // 1.mInfoMediaDevice1:      Last Selected device
@@ -365,7 +376,7 @@
     // 5.mBluetoothMediaDevice2: * 4 times usage not connected
     // 6.mBluetoothMediaDevice3: * 1 time usage
     // 7.mPhoneMediaDevice:      * 0 time usage
-    // Order: 7 -> 1 -> 3 -> 4 -> 6 -> 2 -> 5
+    // Order: 7 -> 1 -> 3 -> 6 -> 4  -> 2 -> 5
     @Test
     public void compareTo_mixedDevices_connectDeviceFirst() {
         when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -394,8 +405,8 @@
         assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
         assertThat(mMediaDevices.get(1)).isEqualTo(mInfoMediaDevice1);
         assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice2);
-        assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice3);
-        assertThat(mMediaDevices.get(4)).isEqualTo(mBluetoothMediaDevice3);
+        assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice3);
+        assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice3);
         assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice1);
         assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice2);
     }
diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
new file mode 100644
index 0000000..a793680
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
@@ -0,0 +1,21 @@
+<?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">
+        <ripple android:color="#99999999" />
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/packages/SystemUI/res/layout/bubble_manage_menu.xml
new file mode 100644
index 0000000..129282d
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_manage_menu.xml
@@ -0,0 +1,99 @@
+<?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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@drawable/rounded_bg_full"
+    android:elevation="@dimen/bubble_manage_menu_elevation"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/bubble_manage_menu_dismiss_container"
+        android:background="@drawable/bubble_manage_menu_row"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_vertical"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_remove_no_shadow"
+            android:tint="@color/global_actions_text"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
+            android:text="@string/bubble_dismiss_text" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/bubble_manage_menu_dont_bubble_container"
+        android:background="@drawable/bubble_manage_menu_row"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_vertical"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_stop_bubble"
+            android:tint="@color/global_actions_text"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
+            android:text="@string/bubbles_dont_bubble_conversation" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/bubble_manage_menu_settings_container"
+        android:background="@drawable/bubble_manage_menu_row"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_vertical"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/bubble_manage_menu_settings_icon"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:src="@drawable/ic_remove_no_shadow"/>
+
+        <TextView
+            android:id="@+id/bubble_manage_menu_settings_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index ae57563..6da96d1 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -17,6 +17,7 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/controls_management_root"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 599ed16..179f8b8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1014,6 +1014,9 @@
     <!-- Margins at the left and right of the power menu and home controls widgets. -->
     <dimen name="global_actions_side_margin">16dp</dimen>
 
+    <!-- Amount to shift the layout when exiting/entering for controls activities -->
+    <dimen name="global_actions_controls_y_translation">20dp</dimen>
+
     <!-- The maximum offset in either direction that elements are moved horizontally to prevent
          burn-in on AOD. -->
     <dimen name="burn_in_prevention_offset_x">8dp</dimen>
@@ -1197,6 +1200,7 @@
          snap to the dismiss target. -->
     <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
     <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+    <dimen name="bubble_manage_menu_elevation">4dp</dimen>
 
     <dimen name="dismiss_circle_size">52dp</dimen>
     <dimen name="dismiss_target_x_size">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cb20e7a..d639ed0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2629,6 +2629,8 @@
     <string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
     <!-- Button text for dismissing the bubble "manage" button tool tip  [CHAR LIMIT=20]-->
     <string name="bubbles_user_education_got_it">Got it</string>
+    <!-- Label for the button that takes the user to the notification settings for the given app. -->
+    <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
 
     <!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] -->
     <string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7e24f5d..4ed819e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -663,8 +663,13 @@
 
     <!-- Controls styles -->
     <style name="Theme.ControlsManagement" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+        <item name="android:windowActivityTransitions">true</item>
+        <item name="android:windowContentTransitions">false</item>
         <item name="android:windowIsTranslucent">false</item>
-        <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
+        <item name="android:windowBackground">@android:color/black</item>
+        <item name="android:colorBackground">@android:color/black</item>
+        <item name="android:windowAnimationStyle">@null</item>
+        <item name="android:statusBarColor">@*android:color/transparent</item>
     </style>
 
     <style name="TextAppearance.Control">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 367058f..a96ef91 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1301,6 +1301,7 @@
     private FingerprintManager mFpm;
     private FaceManager mFaceManager;
     private boolean mFingerprintLockedOut;
+    private TelephonyManager mTelephonyManager;
 
     /**
      * When we receive a
@@ -1728,10 +1729,22 @@
         }
         updateAirplaneModeState();
 
-        TelephonyManager telephony =
+        mTelephonyManager =
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (telephony != null) {
-            telephony.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+        if (mTelephonyManager != null) {
+            mTelephonyManager.listen(mPhoneStateListener,
+                    LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+            // Set initial sim states values.
+            for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
+                int state = mTelephonyManager.getSimState(slot);
+                int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
+                if (subIds != null) {
+                    for (int subId : subIds) {
+                        mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
+                                .sendToTarget();
+                    }
+                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7262f8c..1f27ae2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility;
 
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+
 import android.accessibilityservice.AccessibilityService;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
@@ -282,8 +284,8 @@
 
     private void handleTakeScreenshot() {
         ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
-        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                true, true, new Handler(Looper.getMainLooper()), null);
+        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
+                SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null);
     }
 
     private void handleAccessibilityMenu() {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 71f2bc0..38bfffb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -90,6 +90,7 @@
     }
 
     private FlyoutMessage mFlyoutMessage;
+    private Drawable mBadgedAppIcon;
     private Bitmap mBadgedImage;
     private int mDotColor;
     private Path mDotPath;
@@ -133,6 +134,10 @@
         return mBadgedImage;
     }
 
+    public Drawable getBadgedAppIcon() {
+        return mBadgedAppIcon;
+    }
+
     @Override
     public int getDotColor() {
         return mDotColor;
@@ -239,6 +244,7 @@
         mAppName = info.appName;
         mFlyoutMessage = info.flyoutMessage;
 
+        mBadgedAppIcon = info.badgedAppIcon;
         mBadgedImage = info.badgedBubbleImage;
         mDotColor = info.dotColor;
         mDotPath = info.dotPath;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index c6883c8..e488cf2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -605,6 +605,9 @@
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
             }
+
+            mStackView.setUnbubbleConversationCallback(notificationEntry ->
+                    onUserChangedBubble(notificationEntry, false /* shouldBubble */));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 3524696..bb23655 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -43,7 +43,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
 import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -55,14 +54,13 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
-import com.android.systemui.shared.system.SysUiStatsLog;
 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.
  */
-public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
+public class BubbleExpandedView extends LinearLayout {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
 
     private enum ActivityViewStatus {
@@ -100,9 +98,6 @@
     private int mPointerWidth;
     private int mPointerHeight;
     private ShapeDrawable mPointerDrawable;
-    private Rect mTempRect = new Rect();
-    private int[] mTempLoc = new int[2];
-    private int mExpandedViewTouchSlop;
 
     @Nullable private Bubble mBubble;
 
@@ -224,7 +219,6 @@
         mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
         mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
         mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
-        mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
     }
 
     @Override
@@ -239,7 +233,6 @@
         mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
         mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
 
-
         mPointerDrawable = new ShapeDrawable(TriangleShape.create(
                 mPointerWidth, mPointerHeight, true /* pointUp */));
         mPointerView.setBackground(mPointerDrawable);
@@ -248,7 +241,6 @@
         mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
                 R.dimen.bubble_manage_button_height);
         mSettingsIcon = findViewById(R.id.settings_button);
-        mSettingsIcon.setOnClickListener(this);
 
         mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
                 true /* singleTaskInstance */);
@@ -289,6 +281,19 @@
         return mBubble != null ? mBubble.getEntry() : null;
     }
 
+    void setManageClickListener(OnClickListener manageClickListener) {
+        findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
+    }
+
+    /**
+     * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which
+     * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful
+     * if a view has been added or removed from on top of the ActivityView, such as the manage menu.
+     */
+    void updateObscuredTouchableRegion() {
+        mActivityView.onLocationChanged();
+    }
+
     void applyThemeAttrs() {
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[] {
@@ -473,51 +478,6 @@
     }
 
     /**
-     * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
-     * expanded view.
-     *
-     * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
-     */
-    boolean intersectingTouchableContent(int rawX, int rawY) {
-        mTempRect.setEmpty();
-        if (mActivityView != null) {
-            mTempLoc = mActivityView.getLocationOnScreen();
-            mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
-                    mTempLoc[1] - mExpandedViewTouchSlop,
-                    mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
-                    mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
-        }
-        if (mTempRect.contains(rawX, rawY)) {
-            return true;
-        }
-        mTempLoc = mSettingsIcon.getLocationOnScreen();
-        mTempRect.set(mTempLoc[0],
-                mTempLoc[1],
-                mTempLoc[0] + mSettingsIcon.getWidth(),
-                mTempLoc[1] + mSettingsIcon.getHeight());
-        if (mTempRect.contains(rawX, rawY)) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (mBubble == null) {
-            return;
-        }
-        int id = view.getId();
-        if (id == R.id.settings_button) {
-            Intent intent = mBubble.getSettingsIntent();
-            mStackView.collapseStack(() -> {
-                mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser());
-                logBubbleClickEvent(mBubble,
-                        SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
-            });
-        }
-    }
-
-    /**
      * Update appearance of the expanded view being displayed.
      */
     public void updateView() {
@@ -547,10 +507,8 @@
      * Position of the manage button displayed in the expanded view. Used for placing user
      * education about the manage button.
      */
-    public Rect getManageButtonLocationOnScreen() {
-        mTempLoc = mSettingsIcon.getLocationOnScreen();
-        return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(),
-                mTempLoc[1] + mSettingsIcon.getHeight());
+    public void getManageButtonBoundsOnScreen(Rect rect) {
+        mSettingsIcon.getBoundsOnScreen(rect);
     }
 
     /**
@@ -611,26 +569,4 @@
         }
         return INVALID_DISPLAY;
     }
-
-    /**
-     * Logs bubble UI click event.
-     *
-     * @param bubble the bubble notification entry that user is interacting with.
-     * @param action the user interaction enum.
-     */
-    private void logBubbleClickEvent(Bubble bubble, int action) {
-        StatusBarNotification notification = bubble.getEntry().getSbn();
-        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
-                notification.getPackageName(),
-                notification.getNotification().getChannelId(),
-                notification.getId(),
-                mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
-                mStackView.getBubbleCount(),
-                action,
-                mStackView.getNormalizedXPosition(),
-                mStackView.getNormalizedYPosition(),
-                bubble.showInShade(),
-                bubble.isOngoing(),
-                false /* isAppForeground (unused) */);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0aabdff..c906931 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -33,12 +33,14 @@
 import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -47,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;
@@ -55,6 +58,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -62,6 +66,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.annotation.MainThread;
@@ -83,6 +88,7 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -97,6 +103,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
@@ -224,7 +231,7 @@
     private int mPointerHeight;
     private int mStatusBarHeight;
     private int mImeOffset;
-    private BubbleViewProvider mExpandedBubble;
+    @Nullable private BubbleViewProvider mExpandedBubble;
     private boolean mIsExpanded;
 
     /** Whether the stack is currently on the left side of the screen, or animating there. */
@@ -244,6 +251,10 @@
     }
 
     private BubbleController.BubbleExpandListener mExpandListener;
+
+    /** Callback to run when we want to unbubble the given notification's conversation. */
+    private Consumer<NotificationEntry> mUnbubbleConversationCallback;
+
     private SysUiState mSysUiState;
 
     private boolean mViewUpdatedRequested = false;
@@ -255,9 +266,7 @@
 
     private LayoutInflater mInflater;
 
-    // Used for determining view / touch intersection
-    int[] mTempLoc = new int[2];
-    RectF mTempRect = new RectF();
+    private Rect mTempRect = new Rect();
 
     private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
 
@@ -471,6 +480,11 @@
                 return true;
             }
 
+            // If the manage menu is visible, just hide it.
+            if (mShowingManage) {
+                showManageMenu(false /* show */);
+            }
+
             if (mBubbleData.isExpanded()) {
                 maybeShowManageEducation(false /* show */);
 
@@ -627,6 +641,13 @@
     private BubbleManageEducationView mManageEducationView;
     private boolean mAnimatingManageEducationAway;
 
+    private ViewGroup mManageMenu;
+    private ImageView mManageSettingsIcon;
+    private TextView mManageSettingsText;
+    private boolean mShowingManage = false;
+    private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+
     @SuppressLint("ClickableViewAccessibility")
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer,
@@ -689,6 +710,8 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
+        setUpManageMenu();
+
         setUpFlyout();
         mFlyoutTransitionSpring.setSpring(new SpringForce()
                 .setStiffness(SpringForce.STIFFNESS_LOW)
@@ -838,7 +861,9 @@
         // ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
         setOnTouchListener((view, ev) -> {
             if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-                if (mBubbleData.isExpanded()) {
+                if (mShowingManage) {
+                    showManageMenu(false /* show */);
+                } else if (mBubbleData.isExpanded()) {
                     mBubbleData.setExpanded(false);
                 }
             }
@@ -847,6 +872,66 @@
         });
     }
 
+    private void setUpManageMenu() {
+        if (mManageMenu != null) {
+            removeView(mManageMenu);
+        }
+
+        mManageMenu = (ViewGroup) LayoutInflater.from(getContext()).inflate(
+                R.layout.bubble_manage_menu, this, false);
+        mManageMenu.setVisibility(View.INVISIBLE);
+
+        PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
+
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[] {android.R.attr.dialogCornerRadius});
+        final int menuCornerRadius = ta.getDimensionPixelSize(0, 0);
+        ta.recycle();
+
+        mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius);
+            }
+        });
+        mManageMenu.setClipToOutline(true);
+
+        mManageMenu.findViewById(R.id.bubble_manage_menu_dismiss_container).setOnClickListener(
+                view -> {
+                    showManageMenu(false /* show */);
+                    dismissBubbleIfExists(mBubbleData.getSelectedBubble());
+                });
+
+        mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener(
+                view -> {
+                    showManageMenu(false /* show */);
+                    final Bubble bubble = mBubbleData.getSelectedBubble();
+                    if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+                        mUnbubbleConversationCallback.accept(bubble.getEntry());
+                    }
+                });
+
+        mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+                view -> {
+                    showManageMenu(false /* show */);
+                    final Bubble bubble = mBubbleData.getSelectedBubble();
+                    if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+                        final Intent intent = bubble.getSettingsIntent();
+                        collapseStack(() -> {
+                            mContext.startActivityAsUser(
+                                    intent, bubble.getEntry().getSbn().getUser());
+                            logBubbleClickEvent(
+                                    bubble,
+                                    SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+                        });
+                    }
+                });
+
+        mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon);
+        mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name);
+        addView(mManageMenu);
+    }
+
     private void setUpUserEducation() {
         if (mUserEducationView != null) {
             removeView(mUserEducationView);
@@ -934,6 +1019,7 @@
         setUpFlyout();
         setUpOverflow();
         setUpUserEducation();
+        setUpManageMenu();
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -960,6 +1046,9 @@
                 Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
         addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
+
+        mManageMenu.setVisibility(View.INVISIBLE);
+        mShowingManage = false;
     }
 
     @Override
@@ -1100,6 +1189,12 @@
         mExpandListener = listener;
     }
 
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnbubbleConversationCallback(
+            Consumer<NotificationEntry> unbubbleConversationCallback) {
+        mUnbubbleConversationCallback = unbubbleConversationCallback;
+    }
+
     /**
      * Whether the stack of bubbles is expanded or not.
      */
@@ -1361,15 +1456,14 @@
             mManageEducationView.setAlpha(0);
             mManageEducationView.setVisibility(VISIBLE);
             mManageEducationView.post(() -> {
-                final Rect position =
-                        mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen();
+                mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
                 final int viewHeight = mManageEducationView.getManageViewHeight();
                 final int inset = getResources().getDimensionPixelSize(
                         R.dimen.bubbles_manage_education_top_inset);
                 mManageEducationView.bringToFront();
-                mManageEducationView.setManageViewPosition(position.left,
-                        position.top - viewHeight + inset);
-                mManageEducationView.setPointerPosition(position.centerX() - position.left);
+                mManageEducationView.setManageViewPosition(mTempRect.left,
+                        mTempRect.top - viewHeight + inset);
+                mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left);
                 mManageEducationView.animate()
                         .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
                         .setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
@@ -1443,6 +1537,9 @@
     }
 
     private void animateCollapse() {
+        // Hide the menu if it's visible.
+        showManageMenu(false);
+
         mIsExpanded = false;
         final BubbleViewProvider previouslySelected = mExpandedBubble;
         beforeExpandedViewAnimation();
@@ -1570,9 +1667,9 @@
      */
     @Override
     public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
-        // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch
-        // events from any location.
-        if (mNotificationShadeWindowController.getPanelExpanded()) {
+        // If the notification shade is expanded, or the manage menu is open, we shouldn't let the
+        // ActivityView steal any touch events from any location.
+        if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) {
             touchableRegion.setEmpty();
         }
     }
@@ -1658,17 +1755,20 @@
     private void dismissMagnetizedObject() {
         if (mIsExpanded) {
             final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
-            final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
+            dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));
 
-            if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
-                mBubbleData.notificationEntryRemoved(
-                        draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
-            }
         } else {
             mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         }
     }
 
+    private void dismissBubbleIfExists(@Nullable Bubble bubble) {
+        if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+            mBubbleData.notificationEntryRemoved(
+                    bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+        }
+    }
+
     /** Prepares and starts the desaturate/darken animation on the bubble stack. */
     private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
         mDesaturateAndDarkenTargetView = targetView;
@@ -1912,6 +2012,63 @@
         invalidate();
     }
 
+    private void showManageMenu(boolean show) {
+        mShowingManage = show;
+
+        // This should not happen, since the manage menu is only visible when there's an expanded
+        // bubble. If we end up in this state, just hide the menu immediately.
+        if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+            mManageMenu.setVisibility(View.INVISIBLE);
+            return;
+        }
+
+        // If available, update the manage menu's settings option with the expanded bubble's app
+        // name and icon.
+        if (show && mBubbleData.hasBubbleWithKey(mExpandedBubble.getKey())) {
+            final Bubble bubble = mBubbleData.getBubbleWithKey(mExpandedBubble.getKey());
+            mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon());
+            mManageSettingsText.setText(getResources().getString(
+                    R.string.bubbles_app_settings, bubble.getAppName()));
+        }
+
+        mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
+
+        // When the menu is open, it should be at these coordinates. This will make the menu's
+        // bottom left corner match up with the button's bottom left corner.
+        final float targetX = mTempRect.left;
+        final float targetY = mTempRect.bottom - mManageMenu.getHeight();
+
+        if (show) {
+            mManageMenu.setScaleX(0.5f);
+            mManageMenu.setScaleY(0.5f);
+            mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4);
+            mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4);
+            mManageMenu.setAlpha(0f);
+
+            PhysicsAnimator.getInstance(mManageMenu)
+                    .spring(DynamicAnimation.ALPHA, 1f)
+                    .spring(DynamicAnimation.SCALE_X, 1f)
+                    .spring(DynamicAnimation.SCALE_Y, 1f)
+                    .spring(DynamicAnimation.TRANSLATION_X, targetX)
+                    .spring(DynamicAnimation.TRANSLATION_Y, targetY)
+                    .start();
+
+            mManageMenu.setVisibility(View.VISIBLE);
+        } else {
+            PhysicsAnimator.getInstance(mManageMenu)
+                    .spring(DynamicAnimation.ALPHA, 0f)
+                    .spring(DynamicAnimation.SCALE_X, 0.5f)
+                    .spring(DynamicAnimation.SCALE_Y, 0.5f)
+                    .spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4)
+                    .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4)
+                    .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
+                    .start();
+        }
+
+        // Update the AV's obscured touchable region for the new menu visibility state.
+        mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+    }
+
     private void updateExpandedBubble() {
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updateExpandedBubble()");
@@ -1921,6 +2078,7 @@
                 && mExpandedBubble.getExpandedView() != null) {
             BubbleExpandedView bev = mExpandedBubble.getExpandedView();
             mExpandedViewContainer.addView(bev);
+            bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
             bev.populateExpandedView();
             mExpandedViewContainer.setVisibility(VISIBLE);
             mExpandedViewContainer.setAlpha(1.0f);
@@ -2089,4 +2247,26 @@
         }
         return bubbles;
     }
+
+    /**
+     * Logs bubble UI click event.
+     *
+     * @param bubble the bubble notification entry that user is interacting with.
+     * @param action the user interaction enum.
+     */
+    private void logBubbleClickEvent(Bubble bubble, int action) {
+        StatusBarNotification notification = bubble.getEntry().getSbn();
+        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+                notification.getPackageName(),
+                notification.getNotification().getChannelId(),
+                notification.getId(),
+                getBubbleIndex(getExpandedBubble()),
+                getBubbleCount(),
+                action,
+                getNormalizedXPosition(),
+                getNormalizedYPosition(),
+                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 c96f9a4..8a57a73 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -116,6 +116,7 @@
         ShortcutInfo shortcutInfo;
         String appName;
         Bitmap badgedBubbleImage;
+        Drawable badgedAppIcon;
         int dotColor;
         Path dotPath;
         Bubble.FlyoutMessage flyoutMessage;
@@ -176,6 +177,7 @@
             }
 
             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
+            info.badgedAppIcon = badgedIcon;
             info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
                     badgeBitmapInfo).icon;
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index ef84c73..ca3e2e2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -20,15 +20,17 @@
 import android.graphics.Path;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 /**
  * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
  */
 interface BubbleViewProvider {
-    BubbleExpandedView getExpandedView();
+    @Nullable BubbleExpandedView getExpandedView();
 
     void setContentVisibility(boolean visible);
 
-    View getIconView();
+    @Nullable View getIconView();
 
     void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index);
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
new file mode 100644
index 0000000..4ca47d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.controls.management
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.annotation.IdRes
+import android.content.Intent
+
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+
+import com.android.systemui.Interpolators
+import com.android.systemui.R
+
+import com.android.systemui.controls.ui.ControlsUiController
+
+object ControlsAnimations {
+
+    private const val ALPHA_EXIT_DURATION = 167L
+    private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION
+    private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY
+
+    private const val Y_TRANSLATION_EXIT_DURATION = 183L
+    private const val Y_TRANSLATION_ENTER_DELAY = Y_TRANSLATION_EXIT_DURATION - ALPHA_ENTER_DELAY
+    private const val Y_TRANSLATION_ENTER_DURATION = 400L - Y_TRANSLATION_EXIT_DURATION
+    private var translationY: Float = -1f
+
+    /**
+     * Setup an activity to handle enter/exit animations. [view] should be the root of the content.
+     * Fade and translate together.
+     */
+    fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
+        return object : LifecycleObserver {
+            var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
+
+            init {
+                // Must flag the parent group to move it all together, and set the initial
+                // transitionAlpha to 0.0f. This property is reserved for fade animations.
+                view.setTransitionGroup(true)
+                view.transitionAlpha = 0.0f
+
+                if (translationY == -1f) {
+                    translationY = view.context.resources.getDimensionPixelSize(
+                        R.dimen.global_actions_controls_y_translation).toFloat()
+                }
+            }
+
+            @OnLifecycleEvent(Lifecycle.Event.ON_START)
+            fun setup() {
+                with(window) {
+                    allowEnterTransitionOverlap = true
+                    enterTransition = enterWindowTransition(view.getId())
+                    exitTransition = exitWindowTransition(view.getId())
+                }
+            }
+
+            @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+            fun enterAnimation() {
+                if (showAnimation) {
+                    ControlsAnimations.enterAnimation(view).start()
+                    showAnimation = false
+                }
+            }
+        }
+    }
+
+    fun enterAnimation(view: View): Animator {
+        Log.d(ControlsUiController.TAG, "Enter animation for $view")
+
+        view.transitionAlpha = 0.0f
+        view.alpha = 1.0f
+
+        view.translationY = translationY
+
+        val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f, 1.0f).apply {
+            interpolator = Interpolators.DECELERATE_QUINT
+            startDelay = ALPHA_ENTER_DELAY
+            duration = ALPHA_ENTER_DURATION
+        }
+
+        val yAnimator = ObjectAnimator.ofFloat(view, "translationY", 0.0f).apply {
+            interpolator = Interpolators.DECELERATE_QUINT
+            startDelay = Y_TRANSLATION_ENTER_DURATION
+            duration = Y_TRANSLATION_ENTER_DURATION
+        }
+
+        return AnimatorSet().apply {
+            playTogether(alphaAnimator, yAnimator)
+        }
+    }
+
+    /**
+     * Properly handle animations originating from dialogs. Activity transitions require
+     * transitioning between two activities, so expose this method for dialogs to animate
+     * on exit.
+     */
+    @JvmStatic
+    fun exitAnimation(view: View, onEnd: Runnable? = null): Animator {
+        Log.d(ControlsUiController.TAG, "Exit animation for $view")
+
+        val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f).apply {
+            interpolator = Interpolators.ACCELERATE
+            duration = ALPHA_EXIT_DURATION
+        }
+
+        view.translationY = 0.0f
+        val yAnimator = ObjectAnimator.ofFloat(view, "translationY", -translationY).apply {
+            interpolator = Interpolators.ACCELERATE
+            duration = Y_TRANSLATION_EXIT_DURATION
+        }
+
+        return AnimatorSet().apply {
+            playTogether(alphaAnimator, yAnimator)
+            onEnd?.let {
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        it.run()
+                    }
+                })
+            }
+        }
+    }
+
+    fun enterWindowTransition(@IdRes id: Int) =
+        WindowTransition({ view: View -> enterAnimation(view) }).apply {
+            addTarget(id)
+        }
+
+    fun exitWindowTransition(@IdRes id: Int) =
+        WindowTransition({ view: View -> exitAnimation(view) }).apply {
+            addTarget(id)
+        }
+}
+
+/**
+ * In order to animate, at least one property must be marked on each view that should move.
+ * Setting "item" is just a flag to indicate that it should move by the animator.
+ */
+class WindowTransition(
+    val animator: (view: View) -> Animator
+) : Transition() {
+    override fun captureStartValues(tv: TransitionValues) {
+        tv.values["item"] = 0.0f
+    }
+
+    override fun captureEndValues(tv: TransitionValues) {
+        tv.values["item"] = 1.0f
+    }
+
+    override fun createAnimator(
+        sceneRoot: ViewGroup,
+        startValues: TransitionValues?,
+        endValues: TransitionValues?
+    ): Animator? = animator(startValues!!.view)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index ee1ce7a..273e47c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -16,11 +16,12 @@
 
 package com.android.systemui.controls.management
 
-import android.app.Activity
+import android.app.ActivityOptions
 import android.content.ComponentName
 import android.content.Intent
 import android.os.Bundle
 import android.view.View
+import android.view.ViewGroup
 import android.view.ViewStub
 import android.widget.Button
 import android.widget.TextView
@@ -32,6 +33,7 @@
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.util.LifecycleActivity
 import javax.inject.Inject
 
 /**
@@ -40,7 +42,7 @@
 class ControlsEditingActivity @Inject constructor(
     private val controller: ControlsControllerImpl,
     broadcastDispatcher: BroadcastDispatcher
-) : Activity() {
+) : LifecycleActivity() {
 
     companion object {
         private const val TAG = "ControlsEditingActivity"
@@ -92,6 +94,15 @@
 
     private fun bindViews() {
         setContentView(R.layout.controls_management)
+
+        getLifecycle().addObserver(
+            ControlsAnimations.observerForAnimations(
+                requireViewById<ViewGroup>(R.id.controls_management_root),
+                window,
+                intent
+            )
+        )
+
         requireViewById<ViewStub>(R.id.stub).apply {
             layoutResource = R.layout.controls_management_editing
             inflate()
@@ -113,8 +124,8 @@
                     putExtras(this@ControlsEditingActivity.intent)
                     putExtra(ControlsFavoritingActivity.EXTRA_SINGLE_STRUCTURE, true)
                 }
-                startActivity(intent)
-                finish()
+                startActivity(intent, ActivityOptions
+                    .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle())
             }
         }
 
@@ -151,26 +162,38 @@
         val controls = controller.getFavoritesForStructure(component, structure)
         model = FavoritesModel(component, controls, favoritesModelCallback)
         val elevation = resources.getFloat(R.dimen.control_card_elevation)
-        val adapter = ControlAdapter(elevation)
-        val recycler = requireViewById<RecyclerView>(R.id.list)
+        val recyclerView = requireViewById<RecyclerView>(R.id.list)
+        recyclerView.alpha = 0.0f
+        val adapter = ControlAdapter(elevation).apply {
+            registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+                var hasAnimated = false
+                override fun onChanged() {
+                    if (!hasAnimated) {
+                        hasAnimated = true
+                        ControlsAnimations.enterAnimation(recyclerView).start()
+                    }
+                }
+            })
+        }
+
         val margin = resources
                 .getDimensionPixelSize(R.dimen.controls_card_margin)
         val itemDecorator = MarginItemDecorator(margin, margin)
 
-        recycler.apply {
+        recyclerView.apply {
             this.adapter = adapter
-            layoutManager = GridLayoutManager(recycler.context, 2).apply {
+            layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
                 spanSizeLookup = adapter.spanSizeLookup
             }
             addItemDecoration(itemDecorator)
         }
         adapter.changeModel(model)
         model.attachAdapter(adapter)
-        ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recycler)
+        ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recyclerView)
     }
 
     override fun onDestroy() {
         currentUserTracker.stopTracking()
         super.onDestroy()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 6f34dee..4a34705 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.controls.management
 
-import android.app.Activity
+import android.app.ActivityOptions
 import android.content.ComponentName
 import android.content.Intent
 import android.content.res.Configuration
@@ -41,6 +41,7 @@
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.util.LifecycleActivity
 import java.text.Collator
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -51,7 +52,7 @@
     private val controller: ControlsControllerImpl,
     private val listingController: ControlsListingController,
     broadcastDispatcher: BroadcastDispatcher
-) : Activity() {
+) : LifecycleActivity() {
 
     companion object {
         private const val TAG = "ControlsFavoritingActivity"
@@ -115,6 +116,7 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         val collator = Collator.getInstance(resources.configuration.locales[0])
         comparator = compareBy(collator) { it.structureName }
         appName = intent.getCharSequenceExtra(EXTRA_APP)
@@ -174,12 +176,17 @@
                     pageIndicator.setLocation(0f)
                     pageIndicator.visibility =
                         if (listOfStructures.size > 1) View.VISIBLE else View.GONE
+
+                    ControlsAnimations.enterAnimation(pageIndicator).start()
+                    ControlsAnimations.enterAnimation(structurePager).start()
                 }
             })
         }
     }
 
     private fun setUpPager() {
+        structurePager.alpha = 0.0f
+        pageIndicator.alpha = 0.0f
         structurePager.apply {
             adapter = StructureAdapter(emptyList())
             registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
@@ -203,6 +210,15 @@
 
     private fun bindViews() {
         setContentView(R.layout.controls_management)
+
+        getLifecycle().addObserver(
+            ControlsAnimations.observerForAnimations(
+                requireViewById<ViewGroup>(R.id.controls_management_root),
+                window,
+                intent
+            )
+        )
+
         requireViewById<ViewStub>(R.id.stub).apply {
             layoutResource = R.layout.controls_management_favorites
             inflate()
@@ -278,7 +294,8 @@
                 val i = Intent()
                 i.setComponent(
                     ComponentName(context, ControlsProviderSelectorActivity::class.java))
-                context.startActivity(i)
+                startActivity(i, ActivityOptions
+                    .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle())
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 3be5900..59a8b32 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -16,15 +16,18 @@
 
 package com.android.systemui.controls.management
 
+import android.app.ActivityOptions
 import android.content.ComponentName
 import android.content.Intent
 import android.os.Bundle
 import android.view.LayoutInflater
+import android.view.ViewGroup
 import android.view.ViewStub
 import android.widget.Button
 import android.widget.TextView
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsController
@@ -66,12 +69,23 @@
         super.onCreate(savedInstanceState)
 
         setContentView(R.layout.controls_management)
+
+        getLifecycle().addObserver(
+            ControlsAnimations.observerForAnimations(
+                requireViewById<ViewGroup>(R.id.controls_management_root),
+                window,
+                intent
+            )
+        )
+
         requireViewById<ViewStub>(R.id.stub).apply {
             layoutResource = R.layout.controls_management_apps
             inflate()
         }
 
         recyclerView = requireViewById(R.id.list)
+        recyclerView.alpha = 0.0f
+        recyclerView.layoutManager = LinearLayoutManager(applicationContext)
         recyclerView.adapter = AppAdapter(
                 backExecutor,
                 executor,
@@ -80,11 +94,21 @@
                 LayoutInflater.from(this),
                 ::launchFavoritingActivity,
                 FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
-                resources)
-        recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+                resources).apply {
+            registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+                var hasAnimated = false
+                override fun onChanged() {
+                    if (!hasAnimated) {
+                        hasAnimated = true
+                        ControlsAnimations.enterAnimation(recyclerView).start()
+                    }
+                }
+            })
+        }
 
-        requireViewById<TextView>(R.id.title).text =
-                resources.getText(R.string.controls_providers_title)
+        requireViewById<TextView>(R.id.title).apply {
+            text = resources.getText(R.string.controls_providers_title)
+        }
 
         requireViewById<Button>(R.id.done).setOnClickListener {
             this@ControlsProviderSelectorActivity.finishAffinity()
@@ -98,7 +122,7 @@
      * @param component a component name for a [ControlsProviderService]
      */
     fun launchFavoritingActivity(component: ComponentName?) {
-        backExecutor.execute {
+        executor.execute {
             component?.let {
                 val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
                         .apply {
@@ -107,7 +131,7 @@
                     putExtra(Intent.EXTRA_COMPONENT_NAME, it)
                     flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
                 }
-                startActivity(intent)
+                startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 61a1a98..aed7cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -26,9 +26,10 @@
 
     companion object {
         public const val TAG = "ControlsUiController"
+        public const val EXTRA_ANIMATE = "extra_animate"
     }
 
-    fun show(parent: ViewGroup)
+    fun show(parent: ViewGroup, dismissGlobalActions: Runnable)
     fun hide()
     fun onRefreshState(componentName: ComponentName, controls: List<Control>)
     fun onActionResponse(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 2adfb1b..cfd8df0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -85,7 +85,7 @@
         private const val PREF_STRUCTURE = "controls_structure"
 
         private const val USE_PANELS = "systemui.controls_use_panel"
-        private const val FADE_IN_MILLIS = 225L
+        private const val FADE_IN_MILLIS = 200L
 
         private val EMPTY_COMPONENT = ComponentName("", "")
         private val EMPTY_STRUCTURE = StructureInfo(
@@ -104,6 +104,7 @@
     private var popup: ListPopupWindow? = null
     private var activeDialog: Dialog? = null
     private var hidden = true
+    private lateinit var dismissGlobalActions: Runnable
 
     override val available: Boolean
         get() = controlsController.get().available
@@ -134,9 +135,10 @@
         }
     }
 
-    override fun show(parent: ViewGroup) {
+    override fun show(parent: ViewGroup, dismissGlobalActions: Runnable) {
         Log.d(ControlsUiController.TAG, "show()")
         this.parent = parent
+        this.dismissGlobalActions = dismissGlobalActions
         hidden = false
 
         allStructures = controlsController.get().getFavorites()
@@ -169,7 +171,7 @@
         fadeAnim.setDuration(FADE_IN_MILLIS)
         fadeAnim.addListener(object : AnimatorListenerAdapter() {
             override fun onAnimationEnd(animation: Animator) {
-                show(parent)
+                show(parent, dismissGlobalActions)
                 val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
                 showAnim.setInterpolator(DecelerateInterpolator(1.0f))
                 showAnim.setDuration(FADE_IN_MILLIS)
@@ -256,9 +258,10 @@
     }
 
     private fun startActivity(context: Context, intent: Intent) {
-        val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
-        context.sendBroadcast(closeDialog)
+        // Force animations when transitioning from a dialog to an activity
+        intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
         context.startActivity(intent)
+        dismissGlobalActions.run()
     }
 
     private fun showControlsView(items: List<SelectionItem>) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3226605..b211ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -16,6 +16,8 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 
 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;
@@ -110,6 +112,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.management.ControlsAnimations;
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.controls.ui.ControlsUiController;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -479,7 +482,8 @@
      */
     @VisibleForTesting
     protected int getMaxShownPowerItems() {
-        if (shouldUseControlsLayout()) {
+        // TODO: Overflow disabled on keyguard while we solve for touch blocking issues.
+        if (shouldUseControlsLayout() && !mKeyguardShowing) {
             return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
         } else {
             return Integer.MAX_VALUE;
@@ -829,7 +833,8 @@
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
-                    mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
+                    mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+                            SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
                     mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                     mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
                 }
@@ -1815,7 +1820,7 @@
                 case MESSAGE_DISMISS:
                     if (mDialog != null) {
                         if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
-                            mDialog.dismissImmediately();
+                            mDialog.completeDismiss();
                         } else {
                             mDialog.dismiss();
                         }
@@ -2200,50 +2205,55 @@
                 return WindowInsets.CONSUMED;
             });
             if (mControlsUiController != null) {
-                mControlsUiController.show(mControlsView);
+                mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
             }
         }
 
         @Override
         public void dismiss() {
+            dismissWithAnimation(() -> {
+                mGlobalActionsLayout.setTranslationX(0);
+                mGlobalActionsLayout.setTranslationY(0);
+                mGlobalActionsLayout.setAlpha(1);
+                mGlobalActionsLayout.animate()
+                        .alpha(0)
+                        .translationX(mGlobalActionsLayout.getAnimationOffsetX())
+                        .translationY(mGlobalActionsLayout.getAnimationOffsetY())
+                        .setDuration(550)
+                        .withEndAction(this::completeDismiss)
+                        .setInterpolator(new LogAccelerateInterpolator())
+                        .setUpdateListener(animation -> {
+                            float animatedValue = 1f - animation.getAnimatedFraction();
+                            int alpha = (int) (animatedValue * mScrimAlpha * 255);
+                            mBackgroundDrawable.setAlpha(alpha);
+                            mDepthController.updateGlobalDialogVisibility(animatedValue,
+                                    mGlobalActionsLayout);
+                        })
+                        .start();
+            });
+        }
+
+        private void dismissForControlsActivity() {
+            dismissWithAnimation(() -> {
+                ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
+                ControlsAnimations.exitAnimation(root, this::completeDismiss).start();
+            });
+        }
+
+        void dismissWithAnimation(Runnable animation) {
             if (!mShowing) {
                 return;
             }
             mShowing = false;
-            if (mControlsUiController != null) mControlsUiController.hide();
-            mGlobalActionsLayout.setTranslationX(0);
-            mGlobalActionsLayout.setTranslationY(0);
-            mGlobalActionsLayout.setAlpha(1);
-            mGlobalActionsLayout.animate()
-                    .alpha(0)
-                    .translationX(mGlobalActionsLayout.getAnimationOffsetX())
-                    .translationY(mGlobalActionsLayout.getAnimationOffsetY())
-                    .setDuration(550)
-                    .withEndAction(this::completeDismiss)
-                    .setInterpolator(new LogAccelerateInterpolator())
-                    .setUpdateListener(animation -> {
-                        float animatedValue = 1f - animation.getAnimatedFraction();
-                        int alpha = (int) (animatedValue * mScrimAlpha * 255);
-                        mBackgroundDrawable.setAlpha(alpha);
-                        mDepthController.updateGlobalDialogVisibility(animatedValue,
-                                mGlobalActionsLayout);
-                    })
-                    .start();
-            dismissPanel();
-            dismissOverflow();
-            resetOrientation();
-        }
-
-        void dismissImmediately() {
-            mShowing = false;
-            if (mControlsUiController != null) mControlsUiController.hide();
-            dismissPanel();
-            dismissOverflow();
-            resetOrientation();
-            completeDismiss();
+            animation.run();
         }
 
         private void completeDismiss() {
+            mShowing = false;
+            resetOrientation();
+            dismissPanel();
+            dismissOverflow();
+            if (mControlsUiController != null) mControlsUiController.hide();
             mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
             mDepthController.updateGlobalDialogVisibility(0, null /* view */);
             super.dismiss();
@@ -2304,7 +2314,7 @@
             initializeLayout();
             mGlobalActionsLayout.updateList();
             if (mControlsUiController != null) {
-                mControlsUiController.show(mControlsView);
+                mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
             }
         }
 
@@ -2343,10 +2353,9 @@
                 && mControlsUiController.getAvailable()
                 && !mControlsServiceInfos.isEmpty();
     }
-
     // TODO: Remove legacy layout XML and classes.
     protected boolean shouldUseControlsLayout() {
         // always use new controls layout
         return true;
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f25de6a..233d24b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -26,14 +26,18 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
+import android.graphics.ImageDecoder;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
+import android.media.ThumbnailUtils;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
+import android.net.Uri;
 import android.service.media.MediaBrowserService;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -59,6 +63,7 @@
 import com.android.systemui.qs.QSMediaBrowser;
 import com.android.systemui.util.Assert;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -99,6 +104,13 @@
             com.android.internal.R.id.action4
     };
 
+    // URI fields to try loading album art from
+    private static final String[] ART_URIS = {
+            MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+            MediaMetadata.METADATA_KEY_ART_URI,
+            MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+    };
+
     private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
         @Override
         public void onSessionDestroyed() {
@@ -205,14 +217,16 @@
      * Update the media panel view for the given media session
      * @param token
      * @param iconDrawable
+     * @param largeIcon
      * @param iconColor
      * @param bgColor
      * @param contentIntent
      * @param appNameString
      * @param key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, int iconColor,
-            int bgColor, PendingIntent contentIntent, String appNameString, String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
+            int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
+            String key) {
         // Ensure that component names are updated if token has changed
         if (mToken == null || !mToken.equals(token)) {
             mToken = token;
@@ -303,7 +317,7 @@
         ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
         if (albumView != null) {
             // Resize art in a background thread
-            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
+            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
         }
 
         // Song name
@@ -396,30 +410,82 @@
      * @param albumView view to hold the album art
      */
     protected void processAlbumArt(MediaDescription description, ImageView albumView) {
-        Bitmap albumArt = description.getIconBitmap();
-        //TODO check other fields (b/151054111, b/152067055)
+        Bitmap albumArt = null;
+
+        // First try loading from URI
+        albumArt = loadBitmapFromUri(description.getIconUri());
+
+        // Then check bitmap
+        if (albumArt == null) {
+            albumArt = description.getIconBitmap();
+        }
+
         processAlbumArtInternal(albumArt, albumView);
     }
 
     /**
      * Process album art for layout
      * @param metadata media metadata
+     * @param largeIcon from notification, checked as a fallback if metadata does not have art
      * @param albumView view to hold the album art
      */
-    private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
-        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-        //TODO check other fields (b/151054111, b/152067055)
+    private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) {
+        Bitmap albumArt = null;
+
+        // First look in URI fields
+        for (String field : ART_URIS) {
+            String uriString = metadata.getString(field);
+            if (uriString != null) {
+                albumArt = loadBitmapFromUri(Uri.parse(uriString));
+                if (albumArt != null) {
+                    Log.d(TAG, "loaded art from " + field);
+                    break;
+                }
+            }
+        }
+
+        // Then check bitmap field
+        if (albumArt == null) {
+            albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        }
+
+        // Finally try the notification's largeIcon
+        if (albumArt == null && largeIcon != null) {
+            albumArt = largeIcon.getBitmap();
+        }
+
         processAlbumArtInternal(albumArt, albumView);
     }
 
-    private void processAlbumArtInternal(Bitmap albumArt, ImageView albumView) {
-        float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
+    /**
+     * Load a bitmap from a URI
+     * @param uri
+     * @return bitmap, or null if couldn't be loaded
+     */
+    private Bitmap loadBitmapFromUri(Uri uri) {
+        ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri);
+        try {
+            return ImageDecoder.decodeBitmap(source);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Resize and crop the image if provided and update the control view
+     * @param albumArt Bitmap of art to display, or null to hide view
+     * @param albumView View that will hold the art
+     */
+    private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) {
+        // Resize
         RoundedBitmapDrawable roundedDrawable = null;
         if (albumArt != null) {
+            float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
             Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
             int albumSize = (int) mContext.getResources().getDimension(
                     R.dimen.qs_media_album_size);
-            Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false);
+            Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
             roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
             roundedDrawable.setCornerRadius(radius);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 0f06566..9e574a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -23,6 +23,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
@@ -115,8 +116,8 @@
         }
 
         // Set what we can normally
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName.toString(),
-                null);
+        super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
+                appName.toString(), null);
 
         // Then add info from MediaDescription
         ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
@@ -149,6 +150,7 @@
      * Update media panel view for the given media session
      * @param token token for this media session
      * @param icon app notification icon
+     * @param largeIcon notification's largeIcon, used as a fallback for album art
      * @param iconColor foreground color (for text, icons)
      * @param bgColor background color
      * @param actionsContainer a LinearLayout containing the media action buttons
@@ -156,11 +158,12 @@
      * @param appName Application title
      * @param key original notification's key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor,
-            int bgColor, View actionsContainer, PendingIntent contentIntent, String appName,
-            String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
+            String appName, String key) {
 
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName, key);
+        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
+                key);
 
         // Media controls
         if (actionsContainer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 1eb5778..1252008 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -32,6 +32,7 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
 import android.media.session.MediaSession;
 import android.metrics.LogMaker;
@@ -225,14 +226,16 @@
      * Add or update a player for the associated media session
      * @param token
      * @param icon
+     * @param largeIcon
      * @param iconColor
      * @param bgColor
      * @param actionsContainer
      * @param notif
      * @param key
      */
-    public void addMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
-            View actionsContainer, StatusBarNotification notif, String key) {
+    public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
+            String key) {
         if (!useQsMediaPlayer(mContext)) {
             // Shouldn't happen, but just in case
             Log.e(TAG, "Tried to add media session without player!");
@@ -296,7 +299,7 @@
         Log.d(TAG, "setting player session");
         String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
                 .loadHeaderAppName();
-        player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
+        player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
                 notif.getNotification().contentIntent, appName, key);
 
         if (mMediaPlayers.size() > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 7ba7c5f..89b36da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -19,6 +19,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.view.View;
@@ -58,6 +59,7 @@
      * Update media panel view for the given media session
      * @param token token for this media session
      * @param icon app notification icon
+     * @param largeIcon notification's largeIcon, used as a fallback for album art
      * @param iconColor foreground color (for text, icons)
      * @param bgColor background color
      * @param actionsContainer a LinearLayout containing the media action buttons
@@ -66,8 +68,9 @@
      * @param contentIntent Intent to send when user taps on the view
      * @param key original notification's key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
-            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
+            PendingIntent contentIntent, String key) {
         // Only update if this is a different session and currently playing
         String oldPackage = "";
         if (getController() != null) {
@@ -82,7 +85,7 @@
             return;
         }
 
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, key);
+        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
 
         LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
         int i = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 9cee7e7..1a6a104 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -18,7 +18,9 @@
 
 import android.content.Intent;
 import android.service.quicksettings.Tile;
+import android.text.TextUtils;
 import android.util.Log;
+import android.widget.Switch;
 
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QSTile;
@@ -88,6 +90,10 @@
             state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
             state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
         }
+        state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
+                ? state.label
+                : TextUtils.concat(state.label, ", ", state.secondaryLabel);
+        state.expandedAccessibilityClassName = Switch.class.getName();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66bc177..fecb7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -20,6 +20,7 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
@@ -380,7 +381,7 @@
         public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
                 Insets visibleInsets, int taskId) {
             mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
-                    taskId, mHandler, null);
+                    taskId, SCREENSHOT_OVERVIEW, mHandler, null);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index e1cc0b0..1efe663 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -76,6 +76,7 @@
 import android.widget.LinearLayout;
 import android.widget.Toast;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -104,7 +105,6 @@
      */
     static class SaveImageInBackgroundData {
         public Bitmap image;
-        public Uri imageUri;
         public Consumer<Uri> finisher;
         public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
         public int errorMsgResId;
@@ -112,13 +112,33 @@
 
         void clearImage() {
             image = null;
-            imageUri = null;
+        }
+    }
+
+    /**
+     * Structure returned by the SaveImageInBackgroundTask
+     */
+    static class SavedImageData {
+        public Uri uri;
+        public Notification.Action shareAction;
+        public Notification.Action editAction;
+        public Notification.Action deleteAction;
+        public List<Notification.Action> smartActions;
+
+        /**
+         * Used to reset the return data on error
+         */
+        public void reset() {
+            uri = null;
+            shareAction = null;
+            editAction = null;
+            deleteAction = null;
+            smartActions = null;
         }
     }
 
     abstract static class ActionsReadyListener {
-        abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions,
-                List<Notification.Action> actions);
+        abstract void onActionsReady(SavedImageData imageData);
     }
 
     // These strings are used for communicating the action invoked to
@@ -147,6 +167,7 @@
     private static final int MESSAGE_CORNER_TIMEOUT = 2;
 
     private final ScreenshotNotificationsController mNotificationsController;
+    private final UiEventLogger mUiEventLogger;
 
     private final Context mContext;
     private final WindowManager mWindowManager;
@@ -185,6 +206,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MESSAGE_CORNER_TIMEOUT:
+                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
                     GlobalScreenshot.this.clearScreenshot("timeout");
                     break;
                 default:
@@ -199,9 +221,11 @@
     @Inject
     public GlobalScreenshot(
             Context context, @Main Resources resources, LayoutInflater layoutInflater,
-            ScreenshotNotificationsController screenshotNotificationsController) {
+            ScreenshotNotificationsController screenshotNotificationsController,
+            UiEventLogger uiEventLogger) {
         mContext = context;
         mNotificationsController = screenshotNotificationsController;
+        mUiEventLogger = uiEventLogger;
 
         // Inflate the screenshot layout
         mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
@@ -222,7 +246,10 @@
         mBackgroundProtection = mScreenshotLayout.findViewById(
                 R.id.global_screenshot_actions_background);
         mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
-        mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button"));
+        mDismissButton.setOnClickListener(view -> {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+            clearScreenshot("dismiss_button");
+        });
 
         mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
         mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
@@ -445,12 +472,14 @@
 
         saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
             @Override
-            void onActionsReady(Uri uri, List<Notification.Action> smartActions,
-                    List<Notification.Action> actions) {
-                if (uri == null) {
+            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(() -> {
                         if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
                             mScreenshotAnimation.addListener(
@@ -458,13 +487,12 @@
                                         @Override
                                         public void onAnimationEnd(Animator animation) {
                                             super.onAnimationEnd(animation);
-                                            createScreenshotActionsShadeAnimation(
-                                                    smartActions, actions).start();
+                                            createScreenshotActionsShadeAnimation(imageData)
+                                                    .start();
                                         }
                                     });
                         } else {
-                            createScreenshotActionsShadeAnimation(smartActions,
-                                    actions).start();
+                            createScreenshotActionsShadeAnimation(imageData).start();
                         }
                     });
                 }
@@ -567,8 +595,7 @@
         return dropInAnimation;
     }
 
-    private ValueAnimator createScreenshotActionsShadeAnimation(
-            List<Notification.Action> smartActions, List<Notification.Action> actions) {
+    private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mActionsView.removeAllViews();
         mActionsContainer.setScrollX(0);
@@ -583,45 +610,63 @@
         } catch (RemoteException e) {
         }
 
-        for (Notification.Action smartAction : smartActions) {
+        for (Notification.Action smartAction : imageData.smartActions) {
             ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
                     R.layout.global_screenshot_action_chip, mActionsView, false);
             actionChip.setText(smartAction.title);
             actionChip.setIcon(smartAction.getIcon(), false);
             actionChip.setPendingIntent(smartAction.actionIntent,
-                    () -> clearScreenshot("chip tapped"));
+                    () -> {
+                        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
+                        clearScreenshot("chip tapped");
+                    });
             mActionsView.addView(actionChip);
         }
 
-        for (Notification.Action action : actions) {
-            ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
-                    R.layout.global_screenshot_action_chip, mActionsView, false);
-            actionChip.setText(action.title);
-            actionChip.setIcon(action.getIcon(), true);
-            actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped"));
-            if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) {
-                mScreenshotView.setOnClickListener(v -> {
-                    try {
-                        action.actionIntent.send();
-                        clearScreenshot("screenshot preview tapped");
-                    } catch (PendingIntent.CanceledException e) {
-                        Log.e(TAG, "Intent cancelled", e);
-                    }
-                });
-                mScreenshotView.setContentDescription(action.title);
+        ScreenshotActionChip shareChip = (ScreenshotActionChip) inflater.inflate(
+                R.layout.global_screenshot_action_chip, mActionsView, false);
+        shareChip.setText(imageData.shareAction.title);
+        shareChip.setIcon(imageData.shareAction.getIcon(), true);
+        shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
+            clearScreenshot("chip tapped");
+        });
+        mActionsView.addView(shareChip);
+
+        ScreenshotActionChip editChip = (ScreenshotActionChip) inflater.inflate(
+                R.layout.global_screenshot_action_chip, mActionsView, false);
+        editChip.setText(imageData.editAction.title);
+        editChip.setIcon(imageData.editAction.getIcon(), true);
+        editChip.setPendingIntent(imageData.editAction.actionIntent, () -> {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED);
+            clearScreenshot("chip tapped");
+        });
+        mActionsView.addView(editChip);
+
+        mScreenshotView.setOnClickListener(v -> {
+            try {
+                imageData.editAction.actionIntent.send();
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
+                clearScreenshot("screenshot preview tapped");
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Intent cancelled", e);
             }
-            mActionsView.addView(actionChip);
-        }
+        });
+        mScreenshotView.setContentDescription(imageData.editAction.title);
+
 
         if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
             ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
                     R.layout.global_screenshot_action_chip, mActionsView, false);
             Toast scrollNotImplemented = Toast.makeText(
                     mContext, "Not implemented", Toast.LENGTH_SHORT);
-            scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate
+            scrollChip.setText("Extend"); // TODO : add resource and translate
             scrollChip.setIcon(
                     Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true);
-            scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+            scrollChip.setOnClickListener(v -> {
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
+                scrollNotImplemented.show();
+            });
             mActionsView.addView(scrollChip);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
index f3614ff..095c32f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -24,7 +24,6 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -53,7 +52,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 
-import java.util.List;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -347,14 +345,13 @@
                 // Save the screenshot once we have a bit of time now
                 saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
                     @Override
-                    void onActionsReady(Uri uri, List<Notification.Action> smartActions,
-                            List<Notification.Action> actions) {
-                        if (uri == null) {
+                    void onActionsReady(GlobalScreenshot.SavedImageData actionData) {
+                        if (actionData.uri == null) {
                             mNotificationsController.notifyScreenshotError(
                                     R.string.screenshot_failed_to_capture_text);
                         } else {
                             mNotificationsController
-                                    .showScreenshotActionsNotification(uri, smartActions, actions);
+                                    .showScreenshotActionsNotification(actionData);
                         }
                     }
                 });
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index c828c4c..170174d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -83,6 +83,7 @@
 
     private final Context mContext;
     private final GlobalScreenshot.SaveImageInBackgroundData mParams;
+    private final GlobalScreenshot.SavedImageData mImageData;
     private final String mImageFileName;
     private final long mImageTime;
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
@@ -93,6 +94,7 @@
 
     SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
         mContext = context;
+        mImageData = new GlobalScreenshot.SavedImageData();
 
         // Prepare all the output metadata
         mParams = data;
@@ -145,6 +147,7 @@
             values.put(MediaColumns.IS_PENDING, 1);
 
             final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+
             try {
                 // First, write the actual data for our screenshot
                 try (OutputStream out = resolver.openOutputStream(uri)) {
@@ -192,8 +195,6 @@
                 throw e;
             }
 
-            List<Notification.Action> actions =
-                    populateNotificationActions(mContext, r, uri);
             List<Notification.Action> smartActions = new ArrayList<>();
             if (mSmartActionsEnabled) {
                 int timeoutMs = DeviceConfig.getInt(
@@ -206,8 +207,14 @@
                                 mSmartActionsProvider),
                         mContext));
             }
-            mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions);
-            mParams.imageUri = uri;
+
+            mImageData.uri = uri;
+            mImageData.smartActions = smartActions;
+            mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri);
+            mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri);
+            mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
+
+            mParams.mActionsReadyListener.onActionsReady(mImageData);
             mParams.image = null;
             mParams.errorMsgResId = 0;
         } catch (Exception e) {
@@ -216,29 +223,26 @@
             Slog.e(TAG, "unable to save screenshot", e);
             mParams.clearImage();
             mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
-            mParams.mActionsReadyListener.onActionsReady(null, null, null);
+            mImageData.reset();
+            mParams.mActionsReadyListener.onActionsReady(mImageData);
         }
 
         return null;
     }
 
     @Override
-    protected void onPostExecute(Void params) {
-        mParams.finisher.accept(mParams.imageUri);
-    }
-
-    @Override
     protected void onCancelled(Void params) {
         // If we are cancelled while the task is running in the background, we may get null
         // params. The finisher is expected to always be called back, so just use the baked-in
         // params from the ctor in any case.
-        mParams.mActionsReadyListener.onActionsReady(null, null, null);
+        mImageData.reset();
+        mParams.mActionsReadyListener.onActionsReady(mImageData);
         mParams.finisher.accept(null);
         mParams.clearImage();
     }
 
     @VisibleForTesting
-    List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri) {
+    Notification.Action createShareAction(Context context, Resources r, Uri uri) {
         // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
         // order to do some common work like dismissing the keyguard and sending
         // closeSystemWindows
@@ -263,8 +267,6 @@
         // by setting the (otherwise unused) request code to the current user id.
         int requestCode = context.getUserId();
 
-        ArrayList<Notification.Action> actions = new ArrayList<>();
-
         PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
                 new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
@@ -288,7 +290,15 @@
         Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
                 Icon.createWithResource(r, R.drawable.ic_screenshot_share),
                 r.getString(com.android.internal.R.string.share), shareAction);
-        actions.add(shareActionBuilder.build());
+
+        return shareActionBuilder.build();
+    }
+
+    @VisibleForTesting
+    Notification.Action createEditAction(Context context, Resources r, Uri uri) {
+        // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
+        // order to do some common work like dismissing the keyguard and sending
+        // closeSystemWindows
 
         // Create an edit intent, if a specific package is provided as the editor, then
         // launch that directly
@@ -302,6 +312,10 @@
         editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 
+        // Make sure pending intents for the system user are still unique across users
+        // by setting the (otherwise unused) request code to the current user id.
+        int requestCode = mContext.getUserId();
+
         // Create a edit action
         PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
                 new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
@@ -317,24 +331,30 @@
         Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
                 Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
                 r.getString(com.android.internal.R.string.screenshot_edit), editAction);
-        actions.add(editActionBuilder.build());
 
-        if (mCreateDeleteAction) {
-            // Create a delete action for the notification
-            PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
-                    new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
-                            .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
-                            .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
-                            .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
-                                    mSmartActionsEnabled)
-                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
-            Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
-                    Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
-                    r.getString(com.android.internal.R.string.delete), deleteAction);
-            actions.add(deleteActionBuilder.build());
-        }
-        return actions;
+        return editActionBuilder.build();
+    }
+
+    @VisibleForTesting
+    Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
+        // Make sure pending intents for the system user are still unique across users
+        // by setting the (otherwise unused) request code to the current user id.
+        int requestCode = mContext.getUserId();
+
+        // Create a delete action for the notification
+        PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
+                new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+                        .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
+                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+                                mSmartActionsEnabled)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+        Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
+                Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
+                r.getString(com.android.internal.R.string.delete), deleteAction);
+
+        return deleteActionBuilder.build();
     }
 
     private int getUserHandleOfForegroundApplication(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
new file mode 100644
index 0000000..20fa991
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -0,0 +1,89 @@
+/*
+ * 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.screenshot;
+
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+public enum ScreenshotEvent implements UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "screenshot requested from global actions")
+    SCREENSHOT_REQUESTED_GLOBAL_ACTIONS(302),
+    @UiEvent(doc = "screenshot requested from key chord")
+    SCREENSHOT_REQUESTED_KEY_CHORD(303),
+    @UiEvent(doc = "screenshot requested from other key press (e.g. ctrl-s)")
+    SCREENSHOT_REQUESTED_KEY_OTHER(384),
+    @UiEvent(doc = "screenshot requested from overview")
+    SCREENSHOT_REQUESTED_OVERVIEW(304),
+    @UiEvent(doc = "screenshot requested from accessibility actions")
+    SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS(382),
+    @UiEvent(doc = "screenshot requested (other)")
+    SCREENSHOT_REQUESTED_OTHER(305),
+    @UiEvent(doc = "screenshot was saved")
+    SCREENSHOT_SAVED(306),
+    @UiEvent(doc = "screenshot failed to save")
+    SCREENSHOT_NOT_SAVED(336),
+    @UiEvent(doc = "screenshot preview tapped")
+    SCREENSHOT_PREVIEW_TAPPED(307),
+    @UiEvent(doc = "screenshot edit button tapped")
+    SCREENSHOT_EDIT_TAPPED(308),
+    @UiEvent(doc = "screenshot share button tapped")
+    SCREENSHOT_SHARE_TAPPED(309),
+    @UiEvent(doc = "screenshot smart action chip tapped")
+    SCREENSHOT_SMART_ACTION_TAPPED(374),
+    @UiEvent(doc = "screenshot scroll tapped")
+    SCREENSHOT_SCROLL_TAPPED(373),
+    @UiEvent(doc = "screenshot interaction timed out")
+    SCREENSHOT_INTERACTION_TIMEOUT(310),
+    @UiEvent(doc = "screenshot explicitly dismissed")
+    SCREENSHOT_EXPLICIT_DISMISSAL(311);
+
+    private final int mId;
+
+    ScreenshotEvent(int id) {
+        mId = id;
+    }
+
+    @Override
+    public int getId() {
+        return mId;
+    }
+
+    static ScreenshotEvent getScreenshotSource(int source) {
+        switch (source) {
+            case SCREENSHOT_GLOBAL_ACTIONS:
+                return ScreenshotEvent.SCREENSHOT_REQUESTED_GLOBAL_ACTIONS;
+            case SCREENSHOT_KEY_CHORD:
+                return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD;
+            case SCREENSHOT_KEY_OTHER:
+                return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER;
+            case SCREENSHOT_OVERVIEW:
+                return ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW;
+            case SCREENSHOT_ACCESSIBILITY_ACTIONS:
+                return ScreenshotEvent.SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS;
+            case SCREENSHOT_OTHER:
+            default:
+                return ScreenshotEvent.SCREENSHOT_REQUESTED_OTHER;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 811a8d9..fbcd6ba 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -32,7 +32,6 @@
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Picture;
-import android.net.Uri;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.view.WindowManager;
@@ -42,8 +41,6 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.util.NotificationChannels;
 
-import java.util.List;
-
 import javax.inject.Inject;
 
 /**
@@ -185,23 +182,20 @@
     /**
      * Shows a notification with the saved screenshot and actions that can be taken with it.
      *
-     * @param imageUri URI for the saved image
-     * @param actions  a list of notification actions which can be taken
+     * @param actionData SavedImageData struct with image URI and actions
      */
     public void showScreenshotActionsNotification(
-            Uri imageUri,
-            List<Notification.Action> smartActions,
-            List<Notification.Action> actions) {
-        for (Notification.Action action : actions) {
-            mNotificationBuilder.addAction(action);
-        }
-        for (Notification.Action smartAction : smartActions) {
+            GlobalScreenshot.SavedImageData actionData) {
+        mNotificationBuilder.addAction(actionData.shareAction);
+        mNotificationBuilder.addAction(actionData.editAction);
+        mNotificationBuilder.addAction(actionData.deleteAction);
+        for (Notification.Action smartAction : actionData.smartActions) {
             mNotificationBuilder.addAction(smartAction);
         }
 
         // Create the intent to show the screenshot in gallery
         Intent launchIntent = new Intent(Intent.ACTION_VIEW);
-        launchIntent.setDataAndType(imageUri, "image/png");
+        launchIntent.setDataAndType(actionData.uri, "image/png");
         launchIntent.setFlags(
                 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 8b8b6f8..a6e083b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -32,6 +32,9 @@
 import android.util.Log;
 import android.view.WindowManager;
 
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.ScreenshotHelper;
+
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -42,6 +45,7 @@
     private final GlobalScreenshot mScreenshot;
     private final GlobalScreenshotLegacy mScreenshotLegacy;
     private final UserManager mUserManager;
+    private final UiEventLogger mUiEventLogger;
 
     private Handler mHandler = new Handler(Looper.myLooper()) {
         @Override
@@ -64,14 +68,22 @@
                 return;
             }
 
-            // TODO (mkephart): clean up once notifications flow is fully deprecated
+            // TODO: clean up once notifications flow is fully deprecated
             boolean useCornerFlow = true;
+
+            ScreenshotHelper.ScreenshotRequest screenshotRequest =
+                    (ScreenshotHelper.ScreenshotRequest) msg.obj;
+
+            mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));
+
             switch (msg.what) {
                 case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
                     if (useCornerFlow) {
                         mScreenshot.takeScreenshot(finisher);
                     } else {
-                        mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                        mScreenshotLegacy.takeScreenshot(
+                                finisher, screenshotRequest.getHasStatusBar(),
+                                screenshotRequest.getHasNavBar());
                     }
                     break;
                 case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
@@ -79,17 +91,15 @@
                         mScreenshot.takeScreenshotPartial(finisher);
                     } else {
                         mScreenshotLegacy.takeScreenshotPartial(
-                                finisher, msg.arg1 > 0, msg.arg2 > 0);
+                                finisher, screenshotRequest.getHasStatusBar(),
+                                screenshotRequest.getHasNavBar());
                     }
                     break;
                 case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
-                    Bitmap screenshot = msg.getData().getParcelable(
-                            WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP);
-                    Rect screenBounds = msg.getData().getParcelable(
-                            WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS);
-                    Insets insets = msg.getData().getParcelable(
-                            WindowManager.PARCEL_KEY_SCREENSHOT_INSETS);
-                    int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID);
+                    Bitmap screenshot = screenshotRequest.getBitmap();
+                    Rect screenBounds = screenshotRequest.getBoundsInScreen();
+                    Insets insets = screenshotRequest.getInsets();
+                    int taskId = screenshotRequest.getTaskId();
                     if (useCornerFlow) {
                         mScreenshot.handleImageAsScreenshot(
                                 screenshot, screenBounds, insets, taskId, finisher);
@@ -106,10 +116,12 @@
 
     @Inject
     public TakeScreenshotService(GlobalScreenshot globalScreenshot,
-            GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
+            GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager,
+            UiEventLogger uiEventLogger) {
         mScreenshot = globalScreenshot;
         mScreenshotLegacy = globalScreenshotLegacy;
         mUserManager = userManager;
+        mUiEventLogger = uiEventLogger;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 1b752452..8c24c54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.NavigationBarFragment;
 import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import javax.inject.Inject;
@@ -139,7 +140,8 @@
                     ? Dependency.get(LightBarController.class)
                     : new LightBarController(context,
                             Dependency.get(DarkIconDispatcher.class),
-                            Dependency.get(BatteryController.class));
+                            Dependency.get(BatteryController.class),
+                            Dependency.get(NavigationModeController.class));
             navBar.setLightBarController(lightBarController);
 
             // TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 796f22c..b96cff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -191,6 +191,7 @@
             Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
             panel.getMediaPlayer().setMediaSession(token,
                     iconDrawable,
+                    notif.getLargeIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
@@ -201,6 +202,7 @@
                     com.android.systemui.R.id.quick_settings_panel);
             bigPanel.addMediaSession(token,
                     iconDrawable,
+                    notif.getLargeIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 7d422e3..f9119c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -53,11 +53,13 @@
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -74,7 +76,7 @@
 /**
  * Utility class to handle edge swipes for back gesture
  */
-public class EdgeBackGestureHandler implements DisplayListener,
+public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener,
         PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
 
     private static final String TAG = "EdgeBackGestureHandler";
@@ -165,6 +167,7 @@
     private boolean mIsGesturalModeEnabled;
     private boolean mIsEnabled;
     private boolean mIsNavBarShownTransiently;
+    private boolean mIsBackGestureAllowed;
 
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
@@ -200,7 +203,7 @@
 
     public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
             SysUiState sysUiFlagContainer, PluginManager pluginManager) {
-        final Resources res = context.getResources();
+        super(Dependency.get(BroadcastDispatcher.class));
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
@@ -216,20 +219,30 @@
                 ViewConfiguration.getLongPressTimeout());
 
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
-                mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res));
+                mContext.getMainThreadHandler(), mContext, this::updateCurrentUserResources);
 
-        updateCurrentUserResources(res);
+        updateCurrentUserResources();
         sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
     }
 
-    public void updateCurrentUserResources(Resources res) {
+    public void updateCurrentUserResources() {
+        Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext()
+                .getResources();
         mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
         mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+        mIsBackGestureAllowed =
+                !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
 
         mBottomGestureHeight = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.navigation_bar_gesture_height);
     }
 
+    @Override
+    public void onUserSwitched(int newUserId) {
+        updateIsEnabled();
+        updateCurrentUserResources();
+    }
+
     /**
      * @see NavigationBarView#onAttachedToWindow()
      */
@@ -243,6 +256,7 @@
                 Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME),
                 false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL);
         updateIsEnabled();
+        startTracking();
     }
 
     /**
@@ -255,6 +269,7 @@
         }
         mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver);
         updateIsEnabled();
+        stopTracking();
     }
 
     private void setRotationCallbacks(boolean enable) {
@@ -269,10 +284,13 @@
         }
     }
 
-    public void onNavigationModeChanged(int mode, Context currentUserContext) {
+    /**
+     * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
+     */
+    public void onNavigationModeChanged(int mode) {
         mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
         updateIsEnabled();
-        updateCurrentUserResources(currentUserContext.getResources());
+        updateCurrentUserResources();
     }
 
     public void onNavBarTransientStateChanged(boolean isTransient) {
@@ -363,6 +381,10 @@
         updateDisplaySize();
     }
 
+    public boolean isHandlingGestures() {
+        return mIsEnabled && mIsBackGestureAllowed;
+    }
+
     private WindowManager.LayoutParams createLayoutParams() {
         Resources resources = mContext.getResources();
         WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
@@ -469,9 +491,9 @@
             mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
             mLogGesture = false;
             mInRejectedExclusion = false;
-            mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
-                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
-                    && !mDisabledForQuickstep;
+            mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
+                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
+                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
             if (mAllowGesture) {
                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
                 mEdgeBackPlugin.onMotionEvent(ev);
@@ -599,6 +621,7 @@
     public void dump(PrintWriter pw) {
         pw.println("EdgeBackGestureHandler:");
         pw.println("  mIsEnabled=" + mIsEnabled);
+        pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
         pw.println("  mAllowGesture=" + mAllowGesture);
         pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
         pw.println("  mInRejectedExclusion" + mInRejectedExclusion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 90bc075b..ae7867d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -85,6 +85,8 @@
 import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
 import com.android.systemui.tuner.TunerService;
 
+import java.util.concurrent.Executor;
+
 /**
  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
  * text.
@@ -553,7 +555,7 @@
             }
         };
         if (!mKeyguardStateController.canDismissLockScreen()) {
-            AsyncTask.execute(runnable);
+            Dependency.get(Executor.class).execute(runnable);
         } else {
             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
                     && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d35e1e1..3e5eb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -33,6 +33,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import java.io.FileDescriptor;
@@ -58,6 +59,7 @@
     private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
     private int mStatusBarMode;
     private int mNavigationBarMode;
+    private int mNavigationMode;
     private final Color mDarkModeColor;
 
     /**
@@ -84,11 +86,14 @@
 
     @Inject
     public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher,
-            BatteryController batteryController) {
+            BatteryController batteryController, NavigationModeController navModeController) {
         mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
         mBatteryController = batteryController;
         mBatteryController.addCallback(this);
+        mNavigationMode = navModeController.addListener((mode) -> {
+            mNavigationMode = mode;
+        });
     }
 
     public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -234,7 +239,8 @@
     }
 
     private void updateNavigation() {
-        if (mNavigationBarController != null) {
+        if (mNavigationBarController != null
+                && !QuickStepContract.isGesturalMode(mNavigationMode)) {
             mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 84aecd4..2978772 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -123,7 +123,7 @@
     private KeyButtonDrawable mRecentIcon;
     private KeyButtonDrawable mDockedIcon;
 
-    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
+    private EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final DeadZone mDeadZone;
     private boolean mDeadZoneConsuming = false;
     private final NavigationBarTransitions mBarTransitions;
@@ -244,7 +244,7 @@
     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
         // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
         // gestural mode, the entire nav bar should be touchable.
-        if (!isGesturalMode(mNavBarMode) || mImeVisible) {
+        if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) {
             info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
             return;
         }
@@ -296,8 +296,6 @@
                 R.style.RotateButtonCCWStart90,
                 isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
 
-        final ContextualButton backButton = new ContextualButton(R.id.back, 0);
-
         mConfiguration = new Configuration();
         mTmpLastConfiguration = new Configuration();
         mConfiguration.updateFrom(context.getResources().getConfiguration());
@@ -305,7 +303,7 @@
         mScreenPinningNotify = new ScreenPinningNotify(mContext);
         mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
 
-        mButtonDispatchers.put(R.id.back, backButton);
+        mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
         mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
         mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
@@ -659,7 +657,7 @@
         boolean disableHomeHandle = disableRecent
                 && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
 
-        boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
+        boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
 
         // When screen pinning, don't hide back and home when connected service or back and
@@ -686,9 +684,9 @@
             }
         }
 
-        getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
-        getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
-        getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
+        getBackButton().setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
+        getHomeButton().setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
+        getRecentsButton().setVisibility(disableRecent  ? View.INVISIBLE : View.VISIBLE);
         getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
     }
 
@@ -838,10 +836,9 @@
 
     @Override
     public void onNavigationModeChanged(int mode) {
-        Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext();
         mNavBarMode = mode;
         mBarTransitions.onNavigationModeChanged(mNavBarMode);
-        mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx);
+        mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
         mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
         getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
 
@@ -864,6 +861,7 @@
 
     @Override
     public void onFinishInflate() {
+        super.onFinishInflate();
         mNavigationInflaterView = findViewById(R.id.navigation_inflater);
         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index d24ccf3..6061b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -17,9 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED;
-import static android.os.UserHandle.USER_CURRENT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
@@ -38,17 +35,14 @@
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.util.Log;
-import android.util.SparseBooleanArray;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -70,104 +64,34 @@
     private final Context mContext;
     private Context mCurrentUserContext;
     private final IOverlayManager mOverlayManager;
-    private final DeviceProvisionedController mDeviceProvisionedController;
     private final Executor mUiBgExecutor;
 
-    private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
-
     private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
 
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case ACTION_OVERLAY_CHANGED:
                     if (DEBUG) {
                         Log.d(TAG, "ACTION_OVERLAY_CHANGED");
                     }
                     updateCurrentInteractionMode(true /* notify */);
-                    break;
-            }
         }
     };
 
-    private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onDeviceProvisionedChanged() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onDeviceProvisionedChanged: "
-                                + mDeviceProvisionedController.isDeviceProvisioned());
-                    }
-                    // Once the device has been provisioned, check if we can restore gestural nav
-                    restoreGesturalNavOverlayIfNecessary();
-                }
-
-                @Override
-                public void onUserSetupChanged() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onUserSetupChanged: "
-                                + mDeviceProvisionedController.isCurrentUserSetup());
-                    }
-                    // Once the user has been setup, check if we can restore gestural nav
-                    restoreGesturalNavOverlayIfNecessary();
-                }
-
-                @Override
-                public void onUserSwitched() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onUserSwitched: "
-                                + ActivityManagerWrapper.getInstance().getCurrentUserId());
-                    }
-
-                    // Update the nav mode for the current user
-                    updateCurrentInteractionMode(true /* notify */);
-
-                    // When switching users, defer enabling the gestural nav overlay until the user
-                    // is all set up
-                    deferGesturalNavOverlayIfNecessary();
-                }
-            };
-
     @Inject
-    public NavigationModeController(Context context,
-            DeviceProvisionedController deviceProvisionedController,
-            @UiBackground Executor uiBgExecutor) {
+    public NavigationModeController(Context context, @UiBackground Executor uiBgExecutor) {
         mContext = context;
         mCurrentUserContext = context;
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         mUiBgExecutor = uiBgExecutor;
-        mDeviceProvisionedController = deviceProvisionedController;
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
 
         IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
         overlayFilter.addDataScheme("package");
         overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
 
-        IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED);
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null,
-                null);
-
         updateCurrentInteractionMode(false /* notify */);
-
-        // Check if we need to defer enabling gestural nav
-        deferGesturalNavOverlayIfNecessary();
-    }
-
-    private boolean setGestureModeOverlayForMainLauncher() {
-        if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) {
-            // Already in gesture mode
-            return true;
-        }
-
-        Log.d(TAG, "Switching system navigation to full-gesture mode:"
-                + " contextUser="
-                + mCurrentUserContext.getUserId());
-
-        setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
-        return true;
     }
 
     public void updateCurrentInteractionMode(boolean notify) {
@@ -176,10 +100,9 @@
         if (mode == NAV_BAR_MODE_GESTURAL) {
             switchToDefaultGestureNavOverlayIfNecessary();
         }
-        mUiBgExecutor.execute(() -> {
+        mUiBgExecutor.execute(() ->
             Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
-                    Secure.NAVIGATION_MODE, String.valueOf(mode));
-        });
+                    Secure.NAVIGATION_MODE, String.valueOf(mode)));
         if (DEBUG) {
             Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode);
             dumpAssetPaths(mCurrentUserContext);
@@ -230,61 +153,11 @@
         }
     }
 
-    private void deferGesturalNavOverlayIfNecessary() {
-        final int userId = mDeviceProvisionedController.getCurrentUser();
-        mRestoreGesturalNavBarMode.put(userId, false);
-        if (mDeviceProvisionedController.isDeviceProvisioned()
-                && mDeviceProvisionedController.isCurrentUserSetup()) {
-            // User is already setup and device is provisioned, nothing to do
-            if (DEBUG) {
-                Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
-                        + "setup");
-            }
-            return;
-        }
-
-        ArrayList<String> defaultOverlays = new ArrayList<>();
-        try {
-            defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages()));
-        } catch (RemoteException e) {
-            Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays");
-        }
-        if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) {
-            // No default gesture nav overlay
-            if (DEBUG) {
-                Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
-                        + "default=" + defaultOverlays);
-            }
-            return;
-        }
-
-        // If the default is gestural, force-enable three button mode until the device is
-        // provisioned
-        setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
-        mRestoreGesturalNavBarMode.put(userId, true);
-
-        if (DEBUG) {
-            Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
-        }
-    }
-
-    private void restoreGesturalNavOverlayIfNecessary() {
-        if (DEBUG) {
-            Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore="
-                    + mRestoreGesturalNavBarMode);
-        }
-        final int userId = mDeviceProvisionedController.getCurrentUser();
-        if (mRestoreGesturalNavBarMode.get(userId)) {
-            // Restore the gestural state if necessary
-            setGestureModeOverlayForMainLauncher();
-            mRestoreGesturalNavBarMode.put(userId, false);
-        }
-    }
-
     private void switchToDefaultGestureNavOverlayIfNecessary() {
         final int userId = mCurrentUserContext.getUserId();
         try {
-            final IOverlayManager om = mOverlayManager;
+            final IOverlayManager om = IOverlayManager.Stub.asInterface(
+                    ServiceManager.getService(Context.OVERLAY_SERVICE));
             final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
             if (info != null && !info.isEnabled()) {
                 // Enable the default gesture nav overlay, and move the back gesture inset scale to
@@ -309,20 +182,6 @@
         }
     }
 
-    public void setModeOverlay(String overlayPkg, int userId) {
-        mUiBgExecutor.execute(() -> {
-            try {
-                mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
-                if (DEBUG) {
-                    Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg
-                            + " userId=" + userId);
-                }
-            } catch (SecurityException | IllegalStateException | RemoteException e) {
-                Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId);
-            }
-        });
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NavigationModeController:");
@@ -334,11 +193,6 @@
             defaultOverlays = "failed_to_fetch";
         }
         pw.println("  defaultOverlays=" + defaultOverlays);
-        pw.println("  restoreGesturalNavMode:");
-        for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) {
-            pw.println("    userId=" + mRestoreGesturalNavBarMode.keyAt(i)
-                    + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i));
-        }
         dumpAssetPaths(mCurrentUserContext);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
index 1a6b415..bf52a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
@@ -148,11 +148,6 @@
         updateSamplingRect();
     }
 
-    private void postUpdateSamplingListener() {
-        mHandler.removeCallbacks(mUpdateSamplingListener);
-        mHandler.post(mUpdateSamplingListener);
-    }
-
     private void updateSamplingListener() {
         boolean isSamplingEnabled = mSamplingEnabled
                 && !mSamplingRequestBounds.isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 051bd29..a284335 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -357,7 +357,18 @@
         mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
 
+        // Initial setup of connectivity. Handled as if we had received a sticky broadcast of
+        // ConnectivityManager.CONNECTIVITY_ACTION or ConnectivityManager.INET_CONDITION_ACTION.
+        mReceiverHandler.post(this::updateConnectivity);
+
+        // Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast
+        // of WifiManager.WIFI_STATE_CHANGED_ACTION or WifiManager.NETWORK_STATE_CHANGED_ACTION
+        mReceiverHandler.post(mWifiSignalController::fetchInitialState);
         updateMobileControllers();
+
+        // Initial setup of emergency information. Handled as if we had received a sticky broadcast
+        // of TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
+        mReceiverHandler.post(this::recalculateEmergency);
     }
 
     private void unregisterListeners() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index b258fd4..5257ce4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -102,6 +102,20 @@
     }
 
     /**
+     * Fetches wifi initial state replacing the initial sticky broadcast.
+     */
+    public void fetchInitialState() {
+        mWifiTracker.fetchInitialState();
+        mCurrentState.enabled = mWifiTracker.enabled;
+        mCurrentState.connected = mWifiTracker.connected;
+        mCurrentState.ssid = mWifiTracker.ssid;
+        mCurrentState.rssi = mWifiTracker.rssi;
+        mCurrentState.level = mWifiTracker.level;
+        mCurrentState.statusLabel = mWifiTracker.statusLabel;
+        notifyListenersIfNecessary();
+    }
+
+    /**
      * Extract wifi state directly from broadcasts about changes in wifi state.
      */
     public void handleBroadcast(Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
index d65b285..8880df9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
@@ -115,7 +115,9 @@
 
                 performedLongClick = false
                 handler.postDelayed({
-                    performedLongClick = v.performLongClick()
+                    if (v.isLongClickable) {
+                        performedLongClick = v.performLongClick()
+                    }
                 }, ViewConfiguration.getLongPressTimeout().toLong())
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index 8625d63..db08d64 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -61,14 +61,17 @@
 
 /**
  * Default spring configuration to use for animations where stiffness and/or damping ratio
- * were not provided.
+ * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
  */
-private val defaultSpring = PhysicsAnimator.SpringConfig(
+private val globalDefaultSpring = PhysicsAnimator.SpringConfig(
         SpringForce.STIFFNESS_MEDIUM,
         SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
 
-/** Default fling configuration to use for animations where friction was not provided. */
-private val defaultFling = PhysicsAnimator.FlingConfig(
+/**
+ * Default fling configuration to use for animations where friction was not provided, and a default
+ * fling config was not set via [PhysicsAnimator.setDefaultFlingConfig].
+ */
+private val globalDefaultFling = PhysicsAnimator.FlingConfig(
         friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
 
 /** Whether to log helpful debug information about animations. */
@@ -111,6 +114,12 @@
     /** End actions to run when all animations have completed.  */
     private val endActions = ArrayList<EndAction>()
 
+    /** SpringConfig to use by default for properties whose springs were not provided. */
+    private var defaultSpring: SpringConfig = globalDefaultSpring
+
+    /** FlingConfig to use by default for properties whose fling configs were not provided. */
+    private var defaultFling: FlingConfig = globalDefaultFling
+
     /**
      * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
      * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
@@ -204,6 +213,19 @@
     }
 
     /**
+     * Springs a property to a given value using the provided configuration options, and a start
+     * velocity of 0f.
+     *
+     * @see spring
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float
+    ): PhysicsAnimator<T> {
+        return spring(property, toPosition, 0f)
+    }
+
+    /**
      * Flings a property using the given start velocity, using a [FlingAnimation] configured using
      * the provided configuration settings.
      *
@@ -392,6 +414,14 @@
         return this
     }
 
+    fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
+        this.defaultSpring = defaultSpring
+    }
+
+    fun setDefaultFlingConfig(defaultFling: FlingConfig) {
+        this.defaultFling = defaultFling
+    }
+
     /** Starts the animations! */
     fun start() {
         startAction()
@@ -752,7 +782,7 @@
     ) {
 
         constructor() :
-                this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+                this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
 
         constructor(stiffness: Float, dampingRatio: Float) :
                 this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
@@ -782,10 +812,10 @@
         internal var startVelocity: Float
     ) {
 
-        constructor() : this(defaultFling.friction)
+        constructor() : this(globalDefaultFling.friction)
 
         constructor(friction: Float) :
-                this(friction, defaultFling.min, defaultFling.max)
+                this(friction, globalDefaultFling.min, globalDefaultFling.max)
 
         constructor(friction: Float, min: Float, max: Float) :
                 this(friction, min, max, startVelocity = 0f)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7403a11..6c00eca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -61,6 +61,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
@@ -133,20 +134,23 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
-    private Executor mBackgroundExecutor;
-    @Mock
     private RingerModeTracker mRingerModeTracker;
     @Mock
     private LiveData<Integer> mRingerModeLiveData;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    // Direct executor
+    private Executor mBackgroundExecutor = Runnable::run;
     private TestableLooper mTestableLooper;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private TestableContext mSpiedContext;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        TestableContext context = spy(mContext);
+        mSpiedContext = spy(mContext);
         when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
-        when(context.getPackageManager()).thenReturn(mPackageManager);
+        when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
         doAnswer(invocation -> {
             IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0);
             callback.onChanged(BiometricSourceType.FACE, true /* enabled */,
@@ -161,19 +165,20 @@
         when(mStrongAuthTracker
                 .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
                 .thenReturn(true);
-        context.addMockSystemService(TrustManager.class, mTrustManager);
-        context.addMockSystemService(FingerprintManager.class, mFingerprintManager);
-        context.addMockSystemService(BiometricManager.class, mBiometricManager);
-        context.addMockSystemService(FaceManager.class, mFaceManager);
-        context.addMockSystemService(UserManager.class, mUserManager);
-        context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
-        context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
+        mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
+        mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+        mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+        mSpiedContext.addMockSystemService(FaceManager.class, mFaceManager);
+        mSpiedContext.addMockSystemService(UserManager.class, mUserManager);
+        mSpiedContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+        mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
+        mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
 
         when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
     }
 
     @After
@@ -192,6 +197,22 @@
     }
 
     @Test
+    public void testSimStateInitialized() {
+        final int subId = 3;
+        final int state = TelephonyManager.SIM_STATE_ABSENT;
+
+        when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+        when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
+        when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[] { subId });
+
+        KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext);
+
+        mTestableLooper.processAllMessages();
+
+        assertThat(testKUM.getSimState(subId)).isEqualTo(state);
+    }
+
+    @Test
     public void testIgnoresSimStateCallback_rebroadcast() {
         Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
 
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 9e18e61..3ef693a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -85,6 +85,7 @@
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -183,6 +184,8 @@
     private DumpManager mDumpManager;
     @Mock
     private LockscreenLockIconController mLockIconController;
+    @Mock
+    private NotificationShadeWindowView mNotificationShadeWindowView;
 
     private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private BubbleData mBubbleData;
@@ -219,8 +222,7 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardBypassController, mColorExtractor,
                 mDumpManager);
-        mNotificationShadeWindowController.setNotificationShadeView(
-                mSuperStatusBarViewFactory.getNotificationShadeWindowView());
+        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
         mNotificationShadeWindowController.attach();
 
         // Need notifications for bubbles
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 49236e0..8e6fc8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -79,6 +79,7 @@
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -134,6 +135,8 @@
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private FloatingContentCoordinator mFloatingContentCoordinator;
+    @Mock
+    private NotificationShadeWindowView mNotificationShadeWindowView;
 
     private SysUiState mSysUiState = new SysUiState();
 
@@ -206,8 +209,7 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardBypassController, mColorExtractor,
                 mDumpManager);
-        mNotificationShadeWindowController.setNotificationShadeView(
-                mSuperStatusBarViewFactory.getNotificationShadeWindowView());
+        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
         mNotificationShadeWindowController.attach();
 
         // Need notifications for bubbles
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index e8e98b4..4ac5912d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -133,4 +133,12 @@
 
         verify(mController, times(1)).stopRecording();
     }
+
+    @Test
+    public void testContentDescriptionHasTileName() {
+        mTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mTile.getState().contentDescription.toString().contains(mTile.getState().label));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 11649ca..ffe3cd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.screenshot;
 
+import static org.junit.Assert.assertEquals;
+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.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -40,7 +44,6 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -82,9 +85,9 @@
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
                         smartActionsProvider, true, false);
-        Assert.assertNotNull(smartActionsFuture);
+        assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
-        Assert.assertEquals(Collections.emptyList(), smartActions);
+        assertEquals(Collections.emptyList(), smartActions);
     }
 
     // Tests any exception thrown in waiting for smart actions future to complete does
@@ -99,7 +102,7 @@
                 RuntimeException.class);
         List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
                 "", "", smartActionsFuture, timeoutMs, mSmartActionsProvider);
-        Assert.assertEquals(Collections.emptyList(), actions);
+        assertEquals(Collections.emptyList(), actions);
     }
 
     // Tests any exception thrown in notifying feedback does not affect regular screenshot flow.
@@ -123,9 +126,9 @@
                         mSmartActionsProvider, true, true);
         verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(),
                 eq(false));
-        Assert.assertNotNull(smartActionsFuture);
+        assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
-        Assert.assertEquals(Collections.emptyList(), smartActions);
+        assertEquals(Collections.emptyList(), smartActions);
     }
 
     // Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once.
@@ -152,14 +155,14 @@
                 ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
                         actionsProvider,
                         true, true);
-        Assert.assertNotNull(smartActionsFuture);
+        assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
-        Assert.assertEquals(smartActions.size(), 0);
+        assertEquals(smartActions.size(), 0);
     }
 
-    // Tests for notification action extras.
+    // Tests for share action extras
     @Test
-    public void testNotificationActionExtras() {
+    public void testShareActionExtras() {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
@@ -169,35 +172,70 @@
         data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
         data.finisher = null;
         data.mActionsReadyListener = null;
-        data.createDeleteAction = true;
         SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
-        List<Notification.Action> actions = task.populateNotificationActions(
-                mContext, mContext.getResources(),
+
+        Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
                 Uri.parse("Screenshot_123.png"));
 
-        Assert.assertEquals(actions.size(), 3);
-        boolean isShareFound = false;
-        boolean isEditFound = false;
-        boolean isDeleteFound = false;
-        for (Notification.Action action : actions) {
-            Intent intent = action.actionIntent.getIntent();
-            Assert.assertNotNull(intent);
-            Bundle bundle = intent.getExtras();
-            Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
-            Assert.assertTrue(
-                    bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+        Intent intent = shareAction.actionIntent.getIntent();
+        assertNotNull(intent);
+        Bundle bundle = intent.getExtras();
+        assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+        assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+        assertEquals(GlobalScreenshot.ACTION_TYPE_SHARE, shareAction.title);
+        assertEquals(Intent.ACTION_SEND, intent.getAction());
+    }
 
-            if (action.title.equals(GlobalScreenshot.ACTION_TYPE_DELETE)) {
-                isDeleteFound = intent.getAction() == null;
-            } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_EDIT)) {
-                isEditFound = Intent.ACTION_EDIT.equals(intent.getAction());
-            } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_SHARE)) {
-                isShareFound = Intent.ACTION_SEND.equals(intent.getAction());
-            }
+    // Tests for edit action extras
+    @Test
+    public void testEditActionExtras() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
         }
 
-        Assert.assertTrue(isEditFound);
-        Assert.assertTrue(isDeleteFound);
-        Assert.assertTrue(isShareFound);
+        GlobalScreenshot.SaveImageInBackgroundData
+                data = new GlobalScreenshot.SaveImageInBackgroundData();
+        data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        data.finisher = null;
+        data.mActionsReadyListener = null;
+        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+
+        Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
+                Uri.parse("Screenshot_123.png"));
+
+        Intent intent = editAction.actionIntent.getIntent();
+        assertNotNull(intent);
+        Bundle bundle = intent.getExtras();
+        assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+        assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+        assertEquals(GlobalScreenshot.ACTION_TYPE_EDIT, editAction.title);
+        assertEquals(Intent.ACTION_EDIT, intent.getAction());
+    }
+
+    // Tests for share action extras
+    @Test
+    public void testDeleteActionExtras() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        GlobalScreenshot.SaveImageInBackgroundData
+                data = new GlobalScreenshot.SaveImageInBackgroundData();
+        data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        data.finisher = null;
+        data.mActionsReadyListener = null;
+        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+
+        Notification.Action deleteAction = task.createDeleteAction(mContext,
+                mContext.getResources(),
+                Uri.parse("Screenshot_123.png"));
+
+        Intent intent = deleteAction.actionIntent.getIntent();
+        assertNotNull(intent);
+        Bundle bundle = intent.getExtras();
+        assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+        assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+        assertEquals(deleteAction.title, GlobalScreenshot.ACTION_TYPE_DELETE);
+        assertNull(intent.getAction());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 2b091f2..210744e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -6,11 +6,18 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.AccessibilityController
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.tuner.TunerService
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -24,6 +31,15 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        // Mocked dependencies
+        mDependency.injectMockDependency(AccessibilityController::class.java)
+        mDependency.injectMockDependency(ActivityStarter::class.java)
+        mDependency.injectMockDependency(AssistManager::class.java)
+        mDependency.injectTestDependency(Executor::class.java, Executor { it.run() })
+        mDependency.injectMockDependency(FlashlightController::class.java)
+        mDependency.injectMockDependency(KeyguardStateController::class.java)
+        mDependency.injectMockDependency(TunerService::class.java)
+
         mKeyguardBottomArea = LayoutInflater.from(mContext).inflate(
                 R.layout.keyguard_bottom_area, null, false) as KeyguardBottomAreaView
         mKeyguardBottomArea.setStatusBar(mStatusBar)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 8ab660c..3e46907 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -56,7 +56,7 @@
         when(mStatusBarIconController.getTransitionsController()).thenReturn(
                 mLightBarTransitionsController);
         mLightBarController = new LightBarController(mContext, mStatusBarIconController,
-                mock(BatteryController.class));
+                mock(BatteryController.class), mock(NavigationModeController.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
index 27a5002..14c6e9f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
@@ -17,6 +17,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -50,9 +51,11 @@
         mDependency.injectMockDependency(IWindowManager.class);
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
-        mDependency.injectMockDependency(NavigationModeController.class);
         mDependency.injectMockDependency(StatusBarStateController.class);
         mDependency.injectMockDependency(KeyguardStateController.class);
+        doReturn(mContext)
+                .when(mDependency.injectMockDependency(NavigationModeController.class))
+                .getCurrentUserContext();
 
         NavigationBarView navBar = spy(new NavigationBarView(mContext, null));
         when(navBar.getCurrentView()).thenReturn(navBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 962d773..aef454f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
@@ -222,7 +223,7 @@
 
         ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackArg =
             ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
-        Mockito.verify(mMockCm, atLeastOnce())
+        verify(mMockCm, atLeastOnce())
             .registerDefaultNetworkCallback(callbackArg.capture(), isA(Handler.class));
         mNetworkCallback = callbackArg.getValue();
         assertNotNull(mNetworkCallback);
@@ -384,7 +385,7 @@
     }
 
     protected void verifyHasNoSims(boolean hasNoSimsVisible) {
-        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
+        verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
                 eq(hasNoSimsVisible), eq(false));
     }
 
@@ -395,7 +396,7 @@
         ArgumentCaptor<Boolean> dataInArg = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class);
 
-        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+        verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
                     any(),
                     iconArg.capture(),
                     anyInt(),
@@ -429,7 +430,7 @@
         ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
 
         // TODO: Verify all fields.
-        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+        verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
                 iconArg.capture(),
                 any(),
                 typeIconArg.capture(),
@@ -475,7 +476,7 @@
         ArgumentCaptor<CharSequence> typeContentDescriptionHtmlArg =
                 ArgumentCaptor.forClass(CharSequence.class);
 
-        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+        verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
                 iconArg.capture(),
                 qsIconArg.capture(),
                 typeIconArg.capture(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 9c250c5..988e022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -2,12 +2,14 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Intent;
+import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.wifi.WifiInfo;
@@ -171,6 +173,32 @@
         verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
     }
 
+    @Test
+    public void testFetchInitialData() {
+        mNetworkController.mWifiSignalController.fetchInitialState();
+        Mockito.verify(mMockWm).getWifiState();
+        Mockito.verify(mMockCm).getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+    }
+
+    @Test
+    public void testFetchInitialData_correctValues() {
+        String testSsid = "TEST";
+
+        when(mMockWm.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
+        NetworkInfo networkInfo = mock(NetworkInfo.class);
+        when(networkInfo.isConnected()).thenReturn(true);
+        when(mMockCm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)).thenReturn(networkInfo);
+        WifiInfo wifiInfo = mock(WifiInfo.class);
+        when(wifiInfo.getSSID()).thenReturn(testSsid);
+        when(mMockWm.getConnectionInfo()).thenReturn(wifiInfo);
+
+        mNetworkController.mWifiSignalController.fetchInitialState();
+
+        assertTrue(mNetworkController.mWifiSignalController.mCurrentState.enabled);
+        assertTrue(mNetworkController.mWifiSignalController.mCurrentState.connected);
+        assertEquals(testSsid, mNetworkController.mWifiSignalController.mCurrentState.ssid);
+    }
+
     protected void setWifiActivity(int activity) {
         // TODO: Not this, because this variable probably isn't sticking around.
         mNetworkController.mWifiSignalController.setActivity(activity);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f21f0e7..1a72cf0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2732,7 +2732,7 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
-        new AccessibilityShellCommand(this).exec(this, in, out, err, args,
+        new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
                 callback, resultReceiver);
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index 20a11bd..b36626f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.Process;
 import android.os.ShellCommand;
 import android.os.UserHandle;
 
@@ -28,9 +30,12 @@
  */
 final class AccessibilityShellCommand extends ShellCommand {
     final @NonNull AccessibilityManagerService mService;
+    final @NonNull SystemActionPerformer mSystemActionPerformer;
 
-    AccessibilityShellCommand(@NonNull AccessibilityManagerService service) {
+    AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
+            @NonNull SystemActionPerformer systemActionPerformer) {
         mService = service;
+        mSystemActionPerformer = systemActionPerformer;
     }
 
     @Override
@@ -45,6 +50,9 @@
             case "set-bind-instant-service-allowed": {
                 return runSetBindInstantServiceAllowed();
             }
+            case "call-system-action": {
+                return runCallSystemAction();
+            }
         }
         return -1;
     }
@@ -74,6 +82,22 @@
         return 0;
     }
 
+    private int runCallSystemAction() {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.ROOT_UID
+                && callingUid != Process.SYSTEM_UID
+                && callingUid != Process.SHELL_UID) {
+            return -1;
+        }
+        final String option = getNextArg();
+        if (option != null) {
+            int actionId = Integer.parseInt(option);
+            mSystemActionPerformer.performSystemAction(actionId);
+            return 0;
+        }
+        return -1;
+    }
+
     private Integer parseUserId() {
         final String option = getNextOption();
         if (option != null) {
@@ -97,5 +121,7 @@
         pw.println("    Set whether binding to services provided by instant apps is allowed.");
         pw.println("  get-bind-instant-service-allowed [--user <USER_ID>]");
         pw.println("    Get whether binding to services provided by instant apps is allowed.");
+        pw.println("  call-system-action <ACTION_ID>");
+        pw.println("    Calls the system action with the given action id.");
     }
 }
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index ffae770..a1fc3fa 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
 import android.accessibilityservice.AccessibilityService;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
@@ -305,6 +307,7 @@
                     sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
                     return true;
                 default:
+                    Slog.e(TAG, "Invalid action id: " + actionId);
                     return false;
             }
         } finally {
@@ -398,7 +401,8 @@
         ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
                 ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
         screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                true, true, new Handler(Looper.getMainLooper()), null);
+                true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS,
+                new Handler(Looper.getMainLooper()), null);
         return true;
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b27c5d5..35089d6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -117,6 +117,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * A session for a given activity.
@@ -3079,19 +3080,13 @@
         final boolean isWhitelisted = mService
                 .isWhitelistedForAugmentedAutofillLocked(mComponentName);
 
-        final String historyItem =
-                "aug:id=" + id + " u=" + uid + " m=" + mode
-                + " a=" + ComponentName.flattenToShortString(mComponentName)
-                + " f=" + mCurrentViewId
-                + " s=" + remoteService.getComponentName()
-                + " w=" + isWhitelisted;
-        mService.getMaster().logRequestLocked(historyItem);
-
         if (!isWhitelisted) {
             if (sVerbose) {
                 Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
                         + ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
             }
+            logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+                    mCurrentViewId, isWhitelisted, /*isInline*/null);
             return null;
         }
 
@@ -3116,24 +3111,27 @@
 
         final AutofillId focusedId = mCurrentViewId;
 
+        final Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsResponseCallback =
+                response -> {
+                    synchronized (mLock) {
+                        return mInlineSessionController.onInlineSuggestionsResponseLocked(
+                                focusedId, response);
+                    }
+                };
         final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =
                 (inlineSuggestionsRequest) -> {
-                    remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName,
-                            AutofillId.withoutSession(focusedId),
-                            currentValue, inlineSuggestionsRequest,
-                            /*inlineSuggestionsCallback=*/
-                            response -> {
-                                synchronized (mLock) {
-                                    return mInlineSessionController
-                                            .onInlineSuggestionsResponseLocked(
-                                                    focusedId, response);
-                                }
-                            },
-                            /*onErrorCallback=*/ () -> {
-                                synchronized (mLock) {
-                                    cancelAugmentedAutofillLocked();
-                                }
-                            }, mService.getRemoteInlineSuggestionRenderServiceLocked());
+                    synchronized (mLock) {
+                        logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+                                focusedId, isWhitelisted, inlineSuggestionsRequest != null);
+                        remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName,
+                                AutofillId.withoutSession(focusedId), currentValue,
+                                inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
+                                /*onErrorCallback=*/ () -> {
+                                    synchronized (mLock) {
+                                        cancelAugmentedAutofillLocked();
+                                    }
+                                }, mService.getRemoteInlineSuggestionRenderServiceLocked());
+                    }
                 };
 
         // When the inline suggestion render service is available, there are 2 cases when
@@ -3150,9 +3148,11 @@
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
             remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
                     (extras) -> {
-                        mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
-                                focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
-                                extras);
+                        synchronized (mLock) {
+                            mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+                                    focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
+                                    extras);
+                        }
                     }, mHandler));
         } else {
             requestAugmentedAutofill.accept(
@@ -3165,6 +3165,20 @@
     }
 
     @GuardedBy("mLock")
+    private void logAugmentedAutofillRequestLocked(int mode,
+            ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted,
+            Boolean isInline) {
+        final String historyItem =
+                "aug:id=" + id + " u=" + uid + " m=" + mode
+                        + " a=" + ComponentName.flattenToShortString(mComponentName)
+                        + " f=" + focusedId
+                        + " s=" + augmentedRemoteServiceName
+                        + " w=" + isWhitelisted
+                        + " i=" + isInline;
+        mService.getMaster().logRequestLocked(historyItem);
+    }
+
+    @GuardedBy("mLock")
     private void cancelAugmentedAutofillLocked() {
         final RemoteAugmentedAutofillService remoteService = mService
                 .getRemoteAugmentedAutofillServiceLocked();
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index c27ec66..81de29c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -480,6 +480,12 @@
     public abstract void pruneInstantApps();
 
     /**
+     * Prunes the cache of the APKs in the given APEXes.
+     * @param apexPackages The list of APEX packages that may contain APK-in-APEX.
+     */
+    public abstract void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages);
+
+    /**
      * @return The SetupWizard package name.
      */
     public abstract String getSetupWizardPackageName();
@@ -977,4 +983,9 @@
      * Returns if a package name is a valid system package.
      */
     public abstract boolean isSystemPackage(@NonNull String packageName);
+
+    /**
+     * Unblocks uninstall for all packages for the user.
+     */
+    public abstract void clearBlockUninstallForUser(@UserIdInt int userId);
 }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 28f8380..3cf22c8 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -215,7 +215,6 @@
                 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
                 .setDescription(mContext.getResources().getText(
                         R.string.bluetooth_a2dp_audio_route_name).toString())
-                //TODO: Set type correctly (BLUETOOTH_A2DP or HEARING_AID)
                 .setType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)
                 .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .build();
@@ -236,6 +235,8 @@
         // Update volume when the connection state is changed.
         MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route)
                 .setConnectionState(state);
+        builder.setType(btRoute.connectedProfiles.get(BluetoothProfile.HEARING_AID, false)
+                ? MediaRoute2Info.TYPE_HEARING_AID : MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
 
         if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
             int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 0d89997..d8bf9ed 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
 import static android.media.MediaRouter2Utils.getOriginalId;
 import static android.media.MediaRouter2Utils.getProviderId;
@@ -247,6 +249,22 @@
         }
     }
 
+    public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
+            long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
+        Objects.requireNonNull(router, "router must not be null");
+        Objects.requireNonNull(route, "route must not be null");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                notifySessionHintsForCreatingSessionLocked(uniqueRequestId,
+                        router, route, sessionHints);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void selectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
             MediaRoute2Info route) {
         Objects.requireNonNull(router, "router must not be null");
@@ -265,7 +283,6 @@
         }
     }
 
-
     public void deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
             MediaRoute2Info route) {
         Objects.requireNonNull(router, "router must not be null");
@@ -634,12 +651,30 @@
 
         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestCreateSessionOnHandler,
+                obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, /* managerRecord= */ null, route,
+                        uniqueRequestId, routerRecord, route,
                         sessionHints));
     }
 
+    private void notifySessionHintsForCreatingSessionLocked(long uniqueRequestId,
+            @NonNull IMediaRouter2 router,
+            @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+        final IBinder binder = router.asBinder();
+        final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+        if (routerRecord == null) {
+            Slog.w(TAG, "requestCreateSessionWithRouter2ByManagerRequestLocked: "
+                    + "Ignoring unknown router.");
+            return;
+        }
+
+        routerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(UserHandler::requestCreateSessionWithManagerOnHandler,
+                        routerRecord.mUserRecord.mHandler,
+                        uniqueRequestId, routerRecord, route, sessionHints));
+    }
+
     private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
         final IBinder binder = router.asBinder();
@@ -826,12 +861,13 @@
         }
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
-        //TODO(b/152851868): Use MediaRouter2's OnCreateSessionListener to send session hints.
+
+        // Before requesting to the provider, get session hints from the media router.
+        // As a return, media router will request to create a session.
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestCreateSessionOnHandler,
+                obtainMessage(UserHandler::getSessionHintsForCreatingSessionOnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, managerRecord, route,
-                        /* sessionHints= */ null));
+                        uniqueRequestId, routerRecord, managerRecord, route));
     }
 
     private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
@@ -1149,7 +1185,6 @@
                     this, provider, uniqueRequestId, sessionInfo));
         }
 
-
         @Override
         public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
                 @NonNull RoutingSessionInfo sessionInfo) {
@@ -1267,8 +1302,26 @@
             return -1;
         }
 
-        private void requestCreateSessionOnHandler(long uniqueRequestId,
-                @NonNull RouterRecord routerRecord, @Nullable ManagerRecord managerRecord,
+        private void getSessionHintsForCreatingSessionOnHandler(long uniqueRequestId,
+                @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
+                @NonNull MediaRoute2Info route) {
+            SessionCreationRequest request =
+                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
+            mSessionCreationRequests.add(request);
+
+            try {
+                routerRecord.mRouter.getSessionHintsForCreatingSession(uniqueRequestId, route);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "requestGetSessionHintsOnHandler: "
+                        + "Failed to request. Router probably died.");
+                mSessionCreationRequests.remove(request);
+                notifyRequestFailedToManager(managerRecord.mManager,
+                        toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
+            }
+        }
+
+        private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
+                @NonNull RouterRecord routerRecord,
                 @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
@@ -1281,13 +1334,50 @@
             }
 
             SessionCreationRequest request =
-                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
+                    new SessionCreationRequest(routerRecord, uniqueRequestId, route, null);
             mSessionCreationRequests.add(request);
 
             provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
                     route.getOriginalId(), sessionHints);
         }
 
+        private void requestCreateSessionWithManagerOnHandler(long uniqueRequestId,
+                @NonNull RouterRecord routerRecord,
+                @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+            SessionCreationRequest matchingRequest = null;
+            for (SessionCreationRequest request : mSessionCreationRequests) {
+                if (request.mUniqueRequestId == uniqueRequestId) {
+                    matchingRequest = request;
+                    break;
+                }
+            }
+            if (matchingRequest == null) {
+                Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: "
+                        + "Ignoring an unknown request.");
+                return;
+            }
+
+            if (!TextUtils.equals(matchingRequest.mRoute.getId(), route.getId())) {
+                Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: "
+                        + "The given route is different from the requested route.");
+                return;
+            }
+
+            final MediaRoute2Provider provider = findProvider(route.getProviderId());
+            if (provider == null) {
+                Slog.w(TAG, "Ignoring session creation request since no provider found for"
+                        + " given route=" + route);
+
+                mSessionCreationRequests.remove(matchingRequest);
+                notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager,
+                        toOriginalRequestId(uniqueRequestId), REASON_ROUTE_NOT_AVAILABLE);
+                return;
+            }
+
+            provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
+                    route.getOriginalId(), sessionHints);
+        }
+
         // routerRecord can be null if the session is system's or RCN.
         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index d6bf9fb..bf2cc5e 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -487,6 +487,14 @@
 
     // Binder call
     @Override
+    public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
+            long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
+        mService2.notifySessionHintsForCreatingSession(router,
+                uniqueRequestId, route, sessionHints);
+    }
+
+    // Binder call
+    @Override
     public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId,
             MediaRoute2Info route) {
         mService2.selectRouteWithRouter2(router, sessionId, route);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 1345e37..41d7fff 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -19,6 +19,11 @@
 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
 import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -194,19 +199,27 @@
 
     private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
         int name = R.string.default_audio_route_name;
+        int type = TYPE_BUILTIN_SPEAKER;
         if (newRoutes != null) {
             mCurAudioRoutesInfo.mainType = newRoutes.mainType;
-            if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
-                    || (newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+            if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
+                type = TYPE_WIRED_HEADPHONES;
+                name = com.android.internal.R.string.default_audio_route_name_headphones;
+            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+                type = TYPE_WIRED_HEADSET;
                 name = com.android.internal.R.string.default_audio_route_name_headphones;
             } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+                type = TYPE_DOCK;
                 name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
             } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
+                type = TYPE_HDMI;
                 name = com.android.internal.R.string.default_audio_route_name_hdmi;
             } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
+                type = TYPE_USB_DEVICE;
                 name = com.android.internal.R.string.default_audio_route_name_usb;
             }
         }
+
         mDeviceRoute = new MediaRoute2Info.Builder(
                 DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
                 .setVolumeHandling(mAudioManager.isVolumeFixed()
@@ -214,8 +227,7 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                //TODO: Guess the exact type using AudioDevice
-                .setType(TYPE_BUILTIN_SPEAKER)
+                .setType(type)
                 .addFeature(FEATURE_LIVE_AUDIO)
                 .addFeature(FEATURE_LIVE_VIDEO)
                 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index 1d48438..96da649 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -21,7 +21,6 @@
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 
 import android.annotation.NonNull;
-import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
@@ -41,9 +40,18 @@
 /**
  * Helper for querying shortcuts.
  */
-class ShortcutHelper {
+public class ShortcutHelper {
     private static final String TAG = "ShortcutHelper";
 
+    private static final IntentFilter SHARING_FILTER = new IntentFilter();
+    static {
+        try {
+            SHARING_FILTER.addDataType("*/*");
+        } catch (IntentFilter.MalformedMimeTypeException e) {
+            Slog.e(TAG, "Bad mime type", e);
+        }
+    }
+
     /**
      * Listener to call when a shortcut we're tracking has been removed.
      */
@@ -54,7 +62,6 @@
     private LauncherApps mLauncherAppsService;
     private ShortcutListener mShortcutListener;
     private ShortcutServiceInternal mShortcutServiceInternal;
-    private IntentFilter mSharingFilter;
 
     // Key: packageName Value: <shortcutId, notifId>
     private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
@@ -122,12 +129,6 @@
             ShortcutServiceInternal shortcutServiceInternal) {
         mLauncherAppsService = launcherApps;
         mShortcutListener = listener;
-        mSharingFilter = new IntentFilter();
-        try {
-            mSharingFilter.addDataType("*/*");
-        } catch (IntentFilter.MalformedMimeTypeException e) {
-            Slog.e(TAG, "Bad mime type", e);
-        }
         mShortcutServiceInternal = shortcutServiceInternal;
     }
 
@@ -142,7 +143,21 @@
     }
 
     /**
-     * Only returns shortcut info if it's found and if it's {@link ShortcutInfo#isLongLived()}.
+     * Returns whether the given shortcut info is a conversation shortcut.
+     */
+    public static boolean isConversationShortcut(
+            ShortcutInfo shortcutInfo, ShortcutServiceInternal mShortcutServiceInternal,
+            int callingUserId) {
+        if (shortcutInfo == null || !shortcutInfo.isLongLived() || !shortcutInfo.isEnabled()) {
+            return false;
+        }
+        return mShortcutServiceInternal.isSharingShortcut(callingUserId, "android",
+                shortcutInfo.getPackage(), shortcutInfo.getId(), shortcutInfo.getUserId(),
+                SHARING_FILTER);
+    }
+
+    /**
+     * Only returns shortcut info if it's found and if it's a conversation shortcut.
      */
     ShortcutInfo getValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
         if (mLauncherAppsService == null) {
@@ -161,11 +176,7 @@
             ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
                     ? shortcuts.get(0)
                     : null;
-            if (info == null || !info.isLongLived() || !info.isEnabled()) {
-                return null;
-            }
-            if (mShortcutServiceInternal.isSharingShortcut(user.getIdentifier(),
-                    "android", packageName, shortcutId, user.getIdentifier(), mSharingFilter)) {
+            if (isConversationShortcut(info, mShortcutServiceInternal, user.getIdentifier())) {
                 return info;
             }
             return null;
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 7c47cf0..d36d038 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -612,7 +612,7 @@
                     }
                     final int insert = ~loc;
                     System.arraycopy(appIds, insert, buffer, 0, whitelistSize - insert);
-                    appIds[insert] = existingUid;
+                    appIds[insert] = existingAppId;
                     System.arraycopy(buffer, 0, appIds, insert + 1, whitelistSize - insert);
                     whitelistSize++;
                 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2221644..6b1ef3a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -150,7 +150,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -2099,11 +2098,13 @@
                             continue;
                         }
 
-                        mResolvedInstructionSets.add(archSubDir.getName());
-                        List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
-                        if (!oatFiles.isEmpty()) {
-                            mResolvedInheritedFiles.addAll(oatFiles);
+                        File[] files = archSubDir.listFiles();
+                        if (files == null || files.length == 0) {
+                            continue;
                         }
+
+                        mResolvedInstructionSets.add(archSubDir.getName());
+                        mResolvedInheritedFiles.addAll(Arrays.asList(files));
                     }
                 }
             }
@@ -2117,7 +2118,8 @@
                     if (!libDir.exists() || !libDir.isDirectory()) {
                         continue;
                     }
-                    final List<File> libDirsToInherit = new LinkedList<>();
+                    final List<String> libDirsToInherit = new ArrayList<>();
+                    final List<File> libFilesToInherit = new ArrayList<>();
                     for (File archSubDir : libDir.listFiles()) {
                         if (!archSubDir.isDirectory()) {
                             continue;
@@ -2129,14 +2131,24 @@
                             Slog.e(TAG, "Skipping linking of native library directory!", e);
                             // shouldn't be possible, but let's avoid inheriting these to be safe
                             libDirsToInherit.clear();
+                            libFilesToInherit.clear();
                             break;
                         }
-                        if (!mResolvedNativeLibPaths.contains(relLibPath)) {
-                            mResolvedNativeLibPaths.add(relLibPath);
+
+                        File[] files = archSubDir.listFiles();
+                        if (files == null || files.length == 0) {
+                            continue;
                         }
-                        libDirsToInherit.addAll(Arrays.asList(archSubDir.listFiles()));
+
+                        libDirsToInherit.add(relLibPath);
+                        libFilesToInherit.addAll(Arrays.asList(files));
                     }
-                    mResolvedInheritedFiles.addAll(libDirsToInherit);
+                    for (String subDir : libDirsToInherit) {
+                        if (!mResolvedNativeLibPaths.contains(subDir)) {
+                            mResolvedNativeLibPaths.add(subDir);
+                        }
+                    }
+                    mResolvedInheritedFiles.addAll(libFilesToInherit);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9f543b4..7adafe3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -351,6 +351,7 @@
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.dex.ViewCompiler;
+import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
@@ -24232,6 +24233,25 @@
         }
 
         @Override
+        public void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages) {
+            if (mCacheDir == null) {
+                return;
+            }
+
+            final PackageCacher cacher = new PackageCacher(mCacheDir);
+            synchronized (mLock) {
+                for (int i = 0, size = apexPackages.size(); i < size; i++) {
+                    final List<String> apkNames =
+                            mApexManager.getApksInApex(apexPackages.get(i).packageName);
+                    for (int j = 0, apksInApex = apkNames.size(); j < apksInApex; j++) {
+                        final AndroidPackage pkg = getPackage(apkNames.get(j));
+                        cacher.cleanCachedResult(new File(pkg.getCodePath()));
+                    }
+                }
+            }
+        }
+
+        @Override
         public String getSetupWizardPackageName() {
             return mSetupWizardPackage;
         }
@@ -24701,6 +24721,14 @@
             return packageName.equals(
                     PackageManagerService.this.ensureSystemPackageName(packageName));
         }
+
+        @Override
+        public void clearBlockUninstallForUser(@UserIdInt int userId) {
+            synchronized (mLock) {
+                mSettings.clearBlockUninstallLPw(userId);
+                mSettings.writePackageRestrictionsLPr(userId);
+            }
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 44a61d8..ddeab29 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1833,6 +1833,10 @@
         }
     }
 
+    void clearBlockUninstallLPw(int userId) {
+        mBlockUninstallPackages.remove(userId);
+    }
+
     boolean getBlockUninstallLPr(int userId, String packageName) {
         ArraySet<String> packages = mBlockUninstallPackages.get(userId);
         if (packages == null) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index d1d9877..1c1e64d 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -1226,8 +1226,9 @@
             // APEX checks. For single-package sessions, check if they contain an APEX. For
             // multi-package sessions, find all the child sessions that contain an APEX.
             if (hasApex) {
+                final List<PackageInfo> apexPackages;
                 try {
-                    final List<PackageInfo> apexPackages = submitSessionToApexService(session);
+                    apexPackages = submitSessionToApexService(session);
                     for (int i = 0, size = apexPackages.size(); i < size; i++) {
                         validateApexSignature(apexPackages.get(i));
                     }
@@ -1235,6 +1236,10 @@
                     session.setStagedSessionFailed(e.error, e.getMessage());
                     return;
                 }
+
+                final PackageManagerInternal packageManagerInternal =
+                        LocalServices.getService(PackageManagerInternal.class);
+                packageManagerInternal.pruneCachedApksInApex(apexPackages);
             }
 
             notifyPreRebootVerification_Apex_Complete(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fc70af4..4561d2e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -104,6 +104,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
@@ -3153,13 +3154,17 @@
 
     /**
      * Removes the app restrictions file for a specific package and user id, if it exists.
+     *
+     * @return whether there were any restrictions.
      */
-    private static void cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) {
-        File dir = Environment.getUserSystemDirectory(userId);
-        File resFile = new File(dir, packageToRestrictionsFileName(pkg));
+    private static boolean cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) {
+        final File dir = Environment.getUserSystemDirectory(userId);
+        final File resFile = new File(dir, packageToRestrictionsFileName(pkg));
         if (resFile.exists()) {
             resFile.delete();
+            return true;
         }
+        return false;
     }
 
     /**
@@ -4003,17 +4008,24 @@
         if (restrictions != null) {
             restrictions.setDefusable(true);
         }
+        final boolean changed;
         synchronized (mAppRestrictionsLock) {
             if (restrictions == null || restrictions.isEmpty()) {
-                cleanAppRestrictionsForPackageLAr(packageName, userId);
+                changed = cleanAppRestrictionsForPackageLAr(packageName, userId);
             } else {
                 // Write the restrictions to XML
                 writeApplicationRestrictionsLAr(packageName, restrictions, userId);
+                // TODO(b/154323615): avoid unnecessary broadcast when there is no change.
+                changed = true;
             }
         }
 
+        if (!changed) {
+            return;
+        }
+
         // Notify package of changes via an intent - only sent to explicitly registered receivers.
-        Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+        final Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
         changeIntent.setPackage(packageName);
         changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
@@ -4505,6 +4517,8 @@
             switch(cmd) {
                 case "list":
                     return runList(pw, shell);
+                case "list-missing-system-packages":
+                    return runListMissingSystemPackages(pw, shell);
                 default:
                     return shell.handleDefaultCommands(cmd);
             }
@@ -4571,6 +4585,30 @@
         }
     }
 
+    private int runListMissingSystemPackages(PrintWriter pw, Shell shell) {
+        boolean verbose = false;
+        boolean force = false;
+        String opt;
+        while ((opt = shell.getNextOption()) != null) {
+            switch (opt) {
+                case "-v":
+                    verbose = true;
+                    break;
+                case "--force":
+                    force = true;
+                    break;
+                default:
+                    pw.println("Invalid option: " + opt);
+                    return -1;
+            }
+        }
+
+        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ")) {
+            mSystemPackageInstaller.dumpMissingSystemPackages(ipw, force, verbose);
+        }
+        return 0;
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -5143,6 +5181,9 @@
             pw.println("");
             pw.println("  list [-v] [-all]");
             pw.println("    Prints all users on the system.");
+            pw.println("  list-missing-system-packages [-v] [--force]");
+            pw.println("    Prints all system packages that were not explicitly configured to be "
+                    + "installed.");
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 85c2306..cd1087f5 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -22,15 +22,16 @@
 import android.annotation.UserIdInt;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.res.Resources;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -38,7 +39,9 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -263,37 +266,85 @@
         if (!isLogMode(mode) && !isEnforceMode(mode)) {
             return;
         }
-        Slog.v(TAG,  "Checking that all system packages are whitelisted.");
-        final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
-        PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-
-        // Check whether all whitelisted packages are indeed on the system.
-        for (String pkgName : allWhitelistedPackages) {
-            AndroidPackage pkg = pmInt.getPackage(pkgName);
-            if (pkg == null) {
-                Slog.w(TAG, pkgName + " is whitelisted but not present.");
-            } else if (!pkg.isSystem()) {
-                Slog.w(TAG, pkgName + " is whitelisted and present but not a system package.");
-            }
-        }
-
-        // Check whether all system packages are indeed whitelisted.
-        if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+        final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
+        final int size = warnings.size();
+        if (size == 0) {
+            Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): no warnings");
             return;
         }
-        final boolean doWtf = isEnforceMode(mode);
-        pmInt.forEachPackage(pkg -> {
-            if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.getManifestPackageName())) {
-                final String msg = "System package " + pkg.getManifestPackageName()
-                        + " is not whitelisted using 'install-in-user-type' in SystemConfig "
-                        + "for any user types!";
+
+        if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+            // Only shows whether all whitelisted packages are indeed on the system.
+            for (int i = 0; i < size; i++) {
+                final Pair<Boolean, String> pair = warnings.get(i);
+                final boolean isSevere = pair.first;
+                if (!isSevere) {
+                    final String msg = pair.second;
+                    Slog.w(TAG, msg);
+                }
+            }
+            return;
+        }
+
+        Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): " + size + " warnings");
+        boolean doWtf = !isImplicitWhitelistMode(mode);
+        for (int i = 0; i < size; i++) {
+            final Pair<Boolean, String> pair = warnings.get(i);
+            final boolean isSevere = pair.first;
+            final String msg = pair.second;
+            if (isSevere) {
                 if (doWtf) {
                     Slog.wtf(TAG, msg);
                 } else {
                     Slog.e(TAG, msg);
                 }
+            } else {
+                Slog.w(TAG, msg);
+            }
+        }
+    }
+
+    // TODO: method below was created to refactor the one-time logging logic so it can be used on
+    // dump / cmd as well. It could to be further refactored (for example, creating a new
+    // structure for the warnings so it doesn't need a Pair).
+    /**
+     * Gets warnings for system user whitelisting.
+     *
+     * @return list of warnings, where {@code Pair.first} is the severity ({@code true} for WTF,
+     * {@code false} for WARN) and {@code Pair.second} the message.
+     */
+    @NonNull
+    private List<Pair<Boolean, String>> checkSystemPackagesWhitelistWarnings(
+            @PackageWhitelistMode int mode) {
+        final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
+        final List<Pair<Boolean, String>> warnings = new ArrayList<>();
+        final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+
+        // Check whether all whitelisted packages are indeed on the system.
+        final String notPresentFmt = "%s is whitelisted but not present.";
+        final String notSystemFmt = "%s is whitelisted and present but not a system package.";
+        for (String pkgName : allWhitelistedPackages) {
+            final AndroidPackage pkg = pmInt.getPackage(pkgName);
+            if (pkg == null) {
+                warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName)));
+            } else if (!pkg.isSystem()) {
+                warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName)));
+            }
+        }
+
+        // Check whether all system packages are indeed whitelisted.
+        final String logMessageFmt = "System package %s is not whitelisted using "
+                + "'install-in-user-type' in SystemConfig for any user types!";
+        final boolean isSevere = isEnforceMode(mode);
+        pmInt.forEachPackage(pkg -> {
+            if (!pkg.isSystem()) return;
+            final String pkgName = pkg.getManifestPackageName();
+            if (!allWhitelistedPackages.contains(pkgName)) {
+                warnings.add(new Pair<>(isSevere, String.format(logMessageFmt, pkgName)));
             }
         });
+
+        return warnings;
     }
 
     /** Whether to only install system packages in new users for which they are whitelisted. */
@@ -602,32 +653,45 @@
     }
 
     void dump(PrintWriter pw) {
-        final String prefix = "    ";
-        final String prefix2 = prefix + prefix;
+        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ")) {
+            dumpIndented(ipw);
+        }
+    }
+
+    private void dumpIndented(IndentingPrintWriter pw) {
         final int mode = getWhitelistMode();
         pw.println("Whitelisted packages per user type");
-        pw.print(prefix); pw.print("Mode: ");
+
+        pw.increaseIndent();
+        pw.print("Mode: ");
         pw.print(mode);
         pw.print(isEnforceMode(mode) ? " (enforced)" : "");
         pw.print(isLogMode(mode) ? " (logged)" : "");
         pw.print(isImplicitWhitelistMode(mode) ? " (implicit)" : "");
         pw.print(isIgnoreOtaMode(mode) ? " (ignore OTAs)" : "");
         pw.println();
+        pw.decreaseIndent();
 
-        pw.print(prefix); pw.println("Legend");
+        pw.increaseIndent();
+        pw.println("Legend");
+        pw.increaseIndent();
         for (int idx = 0; idx < mUserTypes.length; idx++) {
-            pw.print(prefix2); pw.println(idx + " -> " + mUserTypes[idx]);
+            pw.println(idx + " -> " + mUserTypes[idx]);
         }
+        pw.decreaseIndent(); pw.decreaseIndent();
 
+        pw.increaseIndent();
         final int size = mWhitelistedPackagesForUserTypes.size();
         if (size == 0) {
-            pw.print(prefix); pw.println("No packages");
+            pw.println("No packages");
+            pw.decreaseIndent();
             return;
         }
-        pw.print(prefix); pw.print(size); pw.println(" packages:");
+        pw.print(size); pw.println(" packages:");
+        pw.increaseIndent();
         for (int pkgIdx = 0; pkgIdx < size; pkgIdx++) {
             final String pkgName = mWhitelistedPackagesForUserTypes.keyAt(pkgIdx);
-            pw.print(prefix2); pw.print(pkgName); pw.print(": ");
+            pw.print(pkgName); pw.print(": ");
             final long userTypesBitSet = mWhitelistedPackagesForUserTypes.valueAt(pkgIdx);
             for (int idx = 0; idx < mUserTypes.length; idx++) {
                 if ((userTypesBitSet & (1 << idx)) != 0) {
@@ -636,5 +700,40 @@
             }
             pw.println();
         }
+        pw.decreaseIndent(); pw.decreaseIndent();
+
+        pw.increaseIndent();
+        dumpMissingSystemPackages(pw, /* force= */ true, /* verbose= */ true);
+        pw.decreaseIndent();
+    }
+
+    void dumpMissingSystemPackages(IndentingPrintWriter pw, boolean force, boolean verbose) {
+        final int mode = getWhitelistMode();
+        final boolean show = force || (isEnforceMode(mode) && !isImplicitWhitelistMode(mode));
+        if (!show) return;
+
+        final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
+        final int size = warnings.size();
+
+        if (size == 0) {
+            if (verbose) {
+                pw.println("All system packages are accounted for");
+            }
+            return;
+        }
+
+        if (verbose) {
+            pw.print(size); pw.println(" warnings for system user:");
+            pw.increaseIndent();
+        }
+        for (int i = 0; i < size; i++) {
+            final Pair<Boolean, String> pair = warnings.get(i);
+            final String lvl = pair.first ? "WTF" : "WARN";
+            final String msg = pair.second;
+            pw.print(lvl); pw.print(": "); pw.println(msg);
+        }
+        if (verbose) {
+            pw.decreaseIndent();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index e5e1b0b..99c6dd1 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PackageParserCacheHelper;
+import android.os.FileUtils;
 import android.os.Parcel;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -197,4 +198,18 @@
             Slog.w(TAG, "Error saving package cache.", e);
         }
     }
+
+    /**
+     * Delete the cache files for the given {@code packageFile}.
+     */
+    public void cleanCachedResult(@NonNull File packageFile) {
+        final String packageName = packageFile.getName();
+        final File[] files = FileUtils.listFilesOrEmpty(mCacheDir,
+                (dir, name) -> name.startsWith(packageName));
+        for (File file : files) {
+            if (!file.delete()) {
+                Slog.e(TAG, "Unable to clean cache file: " + file);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4624e9e..2f84a99 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -62,6 +62,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
 import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
@@ -1328,6 +1330,7 @@
                 mScreenshotChordVolumeDownKeyConsumed = true;
                 cancelPendingPowerKeyAction();
                 mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
+                mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);
                 mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
             }
         }
@@ -1411,14 +1414,19 @@
 
     private class ScreenshotRunnable implements Runnable {
         private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
+        private int mScreenshotSource = SCREENSHOT_KEY_OTHER;
 
         public void setScreenshotType(int screenshotType) {
             mScreenshotType = screenshotType;
         }
 
+        public void setScreenshotSource(int screenshotSource) {
+            mScreenshotSource = screenshotSource;
+        }
+
         @Override
         public void run() {
-            mDefaultDisplayPolicy.takeScreenshot(mScreenshotType);
+            mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource);
         }
     }
 
@@ -2693,6 +2701,7 @@
                 int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
                         : TAKE_SCREENSHOT_FULLSCREEN;
                 mScreenshotRunnable.setScreenshotType(type);
+                mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
                 mHandler.post(mScreenshotRunnable);
                 return -1;
             }
@@ -2709,6 +2718,7 @@
         } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
             if (down && repeatCount == 0) {
                 mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
+                mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
                 mHandler.post(mScreenshotRunnable);
             }
             return -1;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 801d75b..f6d46e2 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -25,6 +25,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
@@ -41,10 +42,13 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
 import com.android.server.PackageWatchdog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.pm.ApexManager;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -71,6 +75,7 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final ApexManager mApexManager;
     private final File mLastStagedRollbackIdsFile;
     // Staged rollback ids that have been committed but their session is not yet ready
     @GuardedBy("mPendingStagedRollbackIds")
@@ -85,6 +90,7 @@
         dataDir.mkdirs();
         mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
         PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+        mApexManager = ApexManager.getInstance();
     }
 
     @Override
@@ -302,6 +308,18 @@
      * Returns true if the package name is the name of a module.
      */
     private boolean isModule(String packageName) {
+        // Check if the package is an APK inside an APEX. If it is, use the parent APEX package when
+        // querying PackageManager.
+        PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        AndroidPackage apkPackage = pmi.getPackage(packageName);
+        if (apkPackage != null) {
+            String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
+                    apkPackage);
+            if (apexPackageName != null) {
+                packageName = apexPackageName;
+            }
+        }
+
         PackageManager pm = mContext.getPackageManager();
         try {
             return pm.getModuleInfo(packageName, 0) != null;
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index a18b690..d98ad74 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -23,6 +23,7 @@
 import android.hardware.soundtrigger.V2_3.RecognitionConfig;
 import android.os.IHwBinder;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -48,83 +49,130 @@
 
     @Override
     public Properties getProperties() {
-        return mUnderlying.getProperties();
+        try {
+            return mUnderlying.getProperties();
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
     }
 
     @Override
     public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback,
             int cookie) {
-        int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback), cookie);
-        synchronized (mModelStates) {
-            mModelStates.put(handle, false);
+        try {
+            int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback),
+                    cookie);
+            synchronized (mModelStates) {
+                mModelStates.put(handle, false);
+            }
+            return handle;
+        } catch (RuntimeException e) {
+            throw handleException(e);
         }
-        return handle;
     }
 
     @Override
     public int loadPhraseSoundModel(ISoundTriggerHw.PhraseSoundModel soundModel, Callback callback,
             int cookie) {
-        int handle = mUnderlying.loadPhraseSoundModel(soundModel, new CallbackEnforcer(callback),
-                cookie);
-        synchronized (mModelStates) {
-            mModelStates.put(handle, false);
+        try {
+            int handle = mUnderlying.loadPhraseSoundModel(soundModel,
+                    new CallbackEnforcer(callback),
+                    cookie);
+            synchronized (mModelStates) {
+                mModelStates.put(handle, false);
+            }
+            return handle;
+        } catch (RuntimeException e) {
+            throw handleException(e);
         }
-        return handle;
     }
 
     @Override
     public void unloadSoundModel(int modelHandle) {
-        mUnderlying.unloadSoundModel(modelHandle);
-        synchronized (mModelStates) {
-            mModelStates.remove(modelHandle);
+        try {
+            mUnderlying.unloadSoundModel(modelHandle);
+            synchronized (mModelStates) {
+                mModelStates.remove(modelHandle);
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
         }
     }
 
     @Override
     public void stopRecognition(int modelHandle) {
-        mUnderlying.stopRecognition(modelHandle);
-        synchronized (mModelStates) {
-            mModelStates.replace(modelHandle, false);
+        try {
+            mUnderlying.stopRecognition(modelHandle);
+            synchronized (mModelStates) {
+                mModelStates.replace(modelHandle, false);
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
         }
     }
 
     @Override
     public void stopAllRecognitions() {
-        mUnderlying.stopAllRecognitions();
-        synchronized (mModelStates) {
-            for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) {
-                entry.setValue(false);
+        try {
+            mUnderlying.stopAllRecognitions();
+            synchronized (mModelStates) {
+                for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) {
+                    entry.setValue(false);
+                }
             }
+        } catch (RuntimeException e) {
+            throw handleException(e);
         }
     }
 
     @Override
     public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback,
             int cookie) {
-        mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback), cookie);
-        synchronized (mModelStates) {
-            mModelStates.replace(modelHandle, true);
+        try {
+            mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback),
+                    cookie);
+            synchronized (mModelStates) {
+                mModelStates.replace(modelHandle, true);
+            }
+        } catch (RuntimeException e) {
+            throw handleException(e);
         }
     }
 
     @Override
     public void getModelState(int modelHandle) {
-        mUnderlying.getModelState(modelHandle);
+        try {
+            mUnderlying.getModelState(modelHandle);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
     }
 
     @Override
     public int getModelParameter(int modelHandle, int param) {
-        return mUnderlying.getModelParameter(modelHandle, param);
+        try {
+            return mUnderlying.getModelParameter(modelHandle, param);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
     }
 
     @Override
     public void setModelParameter(int modelHandle, int param, int value) {
-        mUnderlying.setModelParameter(modelHandle, param, value);
+        try {
+            mUnderlying.setModelParameter(modelHandle, param, value);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
     }
 
     @Override
     public ModelParameterRange queryParameter(int modelHandle, int param) {
-        return mUnderlying.queryParameter(modelHandle, param);
+        try {
+            return mUnderlying.queryParameter(modelHandle, param);
+        } catch (RuntimeException e) {
+            throw handleException(e);
+        }
     }
 
     @Override
@@ -142,6 +190,17 @@
         return mUnderlying.interfaceDescriptor();
     }
 
+    private static RuntimeException handleException(RuntimeException e) {
+        Log.e(TAG, "Exception caught from HAL, rebooting HAL");
+        rebootHal();
+        throw e;
+    }
+
+    private static void rebootHal() {
+        // This property needs to be defined in an init.rc script and trigger a HAL reboot.
+        SystemProperties.set("sys.audio.restart.hal", "1");
+    }
+
     private class CallbackEnforcer implements Callback {
         private final Callback mUnderlying;
 
@@ -157,6 +216,8 @@
             synchronized (mModelStates) {
                 if (!mModelStates.getOrDefault(model, false)) {
                     Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    rebootHal();
+                    return;
                 }
                 if (event.header.status
                         != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
@@ -173,6 +234,8 @@
             synchronized (mModelStates) {
                 if (!mModelStates.getOrDefault(model, false)) {
                     Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    rebootHal();
+                    return;
                 }
                 if (event.common.header.status
                         != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 635cb61..4b464d2 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -27,9 +27,7 @@
 import android.media.soundtrigger_middleware.RecognitionConfig;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.os.Parcel;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.server.SystemService;
@@ -101,28 +99,6 @@
         }
     }
 
-    @Override
-    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-            throws RemoteException {
-        try {
-            return super.onTransact(code, data, reply, flags);
-        } catch (InternalServerError e) {
-            if (e.getCause() instanceof HalException) {
-                // We recover from any sort of HAL failure by rebooting the HAL process.
-                // This will likely reboot more than just the sound trigger HAL.
-                // The rest of the system should be able to tolerate that.
-                rebootHal();
-            }
-            throw e;
-        }
-    }
-
-    private static void rebootHal() {
-        Log.i(TAG, "Rebooting the sound trigger HAL");
-        // This property needs to be defined in an init.rc script and trigger a HAL reboot.
-        SystemProperties.set("sys.audio.restart.hal", "1");
-    }
-
     private final static class ModuleService extends ISoundTriggerModule.Stub {
         private final ISoundTriggerModule mDelegate;
 
@@ -182,22 +158,6 @@
         public void detach() throws RemoteException {
             mDelegate.detach();
         }
-
-        @Override
-        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-                throws RemoteException {
-            try {
-                return super.onTransact(code, data, reply, flags);
-            } catch (InternalServerError e) {
-                if (e.getCause() instanceof HalException) {
-                    // We recover from any sort of HAL failure by rebooting the HAL process.
-                    // This will likely reboot more than just the sound trigger HAL.
-                    // The rest of the system should be able to tolerate that.
-                    rebootHal();
-                }
-                throw e;
-            }
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
new file mode 100644
index 0000000..54ad1d2
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.tv.tunerresourcemanager;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Cas resource object used by the Tuner Resource Manager to record the cas
+ * information.
+ *
+ * @hide
+ */
+public final class CasResource {
+
+    private final int mSystemId;
+
+    private int mMaxSessionNum;
+
+    private int mAvailableSessionNum;
+
+    /**
+     * The owner clients' ids when part of the Cas is occupied.
+     */
+    private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>();
+
+    private CasResource(Builder builder) {
+        this.mSystemId = builder.mSystemId;
+        this.mMaxSessionNum = builder.mMaxSessionNum;
+        this.mAvailableSessionNum = builder.mMaxSessionNum;
+    }
+
+    public int getSystemId() {
+        return mSystemId;
+    }
+
+    public int getMaxSessionNum() {
+        return mMaxSessionNum;
+    }
+
+    public int getUsedSessionNum() {
+        return (mMaxSessionNum - mAvailableSessionNum);
+    }
+
+    public boolean isFullyUsed() {
+        return mAvailableSessionNum == 0;
+    }
+
+    /**
+     * Update max session number.
+     *
+     * @param maxSessionNum the new max session num.
+     */
+    public void updateMaxSessionNum(int maxSessionNum) {
+        mAvailableSessionNum = Math.max(
+                0, mAvailableSessionNum + (maxSessionNum - mMaxSessionNum));
+        mMaxSessionNum = maxSessionNum;
+    }
+
+    /**
+     * Set an owner for the cas
+     *
+     * @param ownerId the client id of the owner.
+     */
+    public void setOwner(int ownerId) {
+        int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId) == null
+                ? 1 : (mOwnerClientIdsToSessionNum.get(ownerId) + 1);
+        mOwnerClientIdsToSessionNum.put(ownerId, sessionNum);
+        mAvailableSessionNum--;
+    }
+
+    /**
+     * Remove an owner of the Cas.
+     *
+     * @param ownerId the removing client id of the owner.
+     */
+    public void removeOwner(int ownerId) {
+        mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
+        mOwnerClientIdsToSessionNum.remove(ownerId);
+    }
+
+    public Set<Integer> getOwnerClientIds() {
+        return mOwnerClientIdsToSessionNum.keySet();
+    }
+
+    @Override
+    public String toString() {
+        return "CasResource[systemId=" + this.mSystemId
+                + ", isFullyUsed=" + (this.mAvailableSessionNum == 0)
+                + ", maxSessionNum=" + this.mMaxSessionNum
+                + ", ownerClients=" + ownersMapToString() + "]";
+    }
+
+    /**
+     * Builder class for {@link CasResource}.
+     */
+    public static class Builder {
+
+        private int mSystemId;
+        private int mMaxSessionNum;
+
+        Builder(int systemId) {
+            this.mSystemId = systemId;
+        }
+
+        /**
+         * Builder for {@link CasResource}.
+         *
+         * @param maxSessionNum the max session num the current Cas has.
+         */
+        public Builder maxSessionNum(int maxSessionNum) {
+            this.mMaxSessionNum = maxSessionNum;
+            return this;
+        }
+
+        /**
+         * Build a {@link CasResource}.
+         *
+         * @return {@link CasResource}.
+         */
+        public CasResource build() {
+            CasResource cas = new CasResource(this);
+            return cas;
+        }
+    }
+
+    private String ownersMapToString() {
+        StringBuilder string = new StringBuilder("{");
+        for (int clienId : mOwnerClientIdsToSessionNum.keySet()) {
+            string.append(" clientId=")
+                  .append(clienId)
+                  .append(", owns session num=")
+                  .append(mOwnerClientIdsToSessionNum.get(clienId))
+                  .append(",");
+        }
+        return string.append("}").toString();
+    }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 4cdc172..2b0fe8a 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -27,6 +27,7 @@
 public final class ClientProfile {
 
     public static final int INVALID_GROUP_ID = -1;
+    public static final int INVALID_RESOURCE_ID = -1;
 
     /**
      * Client id sent to the client when registering with
@@ -56,7 +57,6 @@
      * also lose their resources.
      */
     private int mGroupId = INVALID_GROUP_ID;
-
     /**
      * Optional nice value for TRM to reduce client’s priority.
      */
@@ -73,6 +73,11 @@
     private Set<Integer> mUsingLnbIds = new HashSet<>();
 
     /**
+     * List of the Cas system ids that are used by the current client.
+     */
+    private int mUsingCasSystemId = INVALID_RESOURCE_ID;
+
+    /**
      * Optional arbitrary priority value given by the client.
      *
      * <p>This value can override the default priorotiy calculated from
@@ -172,11 +177,32 @@
     }
 
     /**
+     * Set when the client starts to use a Cas system.
+     *
+     * @param casSystemId cas being used.
+     */
+    public void useCas(int casSystemId) {
+        mUsingCasSystemId = casSystemId;
+    }
+
+    public int getInUseCasSystemId() {
+        return mUsingCasSystemId;
+    }
+
+    /**
+     * Called when the client released a Cas System.
+     */
+    public void releaseCas() {
+        mUsingCasSystemId = INVALID_RESOURCE_ID;
+    }
+
+    /**
      * Called to reclaim all the resources being used by the current client.
      */
     public void reclaimAllResources() {
         mUsingFrontendIds.clear();
         mUsingLnbIds.clear();
+        mUsingCasSystemId = INVALID_RESOURCE_ID;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 7231813..2f70840 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -65,6 +65,8 @@
     private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
     // Map of the current available lnb resources
     private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
+    // Map of the current available Cas resources
+    private Map<Integer, CasResource> mCasResources = new HashMap<>();
 
     @GuardedBy("mLock")
     private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>();
@@ -158,10 +160,8 @@
         @Override
         public void updateCasInfo(int casSystemId, int maxSessionNum) {
             enforceTrmAccessPermission("updateCasInfo");
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "updateCasInfo(casSystemId=" + casSystemId
-                                + ", maxSessionNum=" + maxSessionNum + ")");
+            synchronized (mLock) {
+                updateCasInfoInternal(casSystemId, maxSessionNum);
             }
         }
 
@@ -185,11 +185,11 @@
                 throw new RemoteException("frontendHandle can't be null");
             }
             synchronized (mLock) {
-                try {
-                    return requestFrontendInternal(request, frontendHandle);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request frontend from unregistered client:"
+                            + request.getClientId());
                 }
+                return requestFrontendInternal(request, frontendHandle);
             }
         }
 
@@ -211,32 +211,45 @@
                 throw new RemoteException("demuxHandle can't be null");
             }
             synchronized (mLock) {
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request demux from unregistered client:"
+                            + request.getClientId());
+                }
                 return requestDemuxInternal(request, demuxHandle);
             }
         }
 
         @Override
         public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
-                    @NonNull int[] descrambleHandle) throws RemoteException {
+                    @NonNull int[] descramblerHandle) throws RemoteException {
             enforceDescramblerAccessPermission("requestDescrambler");
             enforceTrmAccessPermission("requestDescrambler");
-            if (descrambleHandle == null) {
-                throw new RemoteException("descrambleHandle can't be null");
+            if (descramblerHandle == null) {
+                throw new RemoteException("descramblerHandle can't be null");
             }
             synchronized (mLock) {
-                return requestDescramblerInternal(request, descrambleHandle);
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request descrambler from unregistered client:"
+                            + request.getClientId());
+                }
+                return requestDescramblerInternal(request, descramblerHandle);
             }
         }
 
         @Override
-        public boolean requestCasSession(
-                @NonNull CasSessionRequest request, @NonNull int[] sessionResourceHandle) {
+        public boolean requestCasSession(@NonNull CasSessionRequest request,
+                @NonNull int[] casSessionHandle) throws RemoteException {
             enforceTrmAccessPermission("requestCasSession");
-            if (DEBUG) {
-                Slog.d(TAG, "requestCasSession(request=" + request + ")");
+            if (casSessionHandle == null) {
+                throw new RemoteException("casSessionHandle can't be null");
             }
-
-            return true;
+            synchronized (mLock) {
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request cas from unregistered client:"
+                            + request.getClientId());
+                }
+                return requestCasSessionInternal(request, casSessionHandle);
+            }
         }
 
         @Override
@@ -248,11 +261,11 @@
                 throw new RemoteException("lnbHandle can't be null");
             }
             synchronized (mLock) {
-                try {
-                    return requestLnbInternal(request, lnbHandle);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request lnb from unregistered client:"
+                            + request.getClientId());
                 }
+                return requestLnbInternal(request, lnbHandle);
             }
         }
 
@@ -264,15 +277,20 @@
                     frontendHandle)) {
                 throw new RemoteException("frontendHandle can't be invalid");
             }
-            int frontendId = getResourceIdFromHandle(frontendHandle);
-            FrontendResource fe = getFrontendResource(frontendId);
-            if (fe == null) {
-                throw new RemoteException("Releasing frontend does not exist.");
-            }
-            if (fe.getOwnerClientId() != clientId) {
-                throw new RemoteException("Client is not the current owner of the releasing fe.");
-            }
             synchronized (mLock) {
+                if (!checkClientExists(clientId)) {
+                    throw new RemoteException("Release frontend from unregistered client:"
+                            + clientId);
+                }
+                int frontendId = getResourceIdFromHandle(frontendHandle);
+                FrontendResource fe = getFrontendResource(frontendId);
+                if (fe == null) {
+                    throw new RemoteException("Releasing frontend does not exist.");
+                }
+                if (fe.getOwnerClientId() != clientId) {
+                    throw new RemoteException(
+                            "Client is not the current owner of the releasing fe.");
+                }
                 releaseFrontendInternal(fe);
             }
         }
@@ -296,10 +314,26 @@
         }
 
         @Override
-        public void releaseCasSession(int sessionResourceId, int clientId) {
+        public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
             enforceTrmAccessPermission("releaseCasSession");
-            if (DEBUG) {
-                Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")");
+            if (!validateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
+                throw new RemoteException("casSessionHandle can't be invalid");
+            }
+            synchronized (mLock) {
+                if (!checkClientExists(clientId)) {
+                    throw new RemoteException("Release cas from unregistered client:" + clientId);
+                }
+                int casSystemId = getClientProfile(clientId).getInUseCasSystemId();
+                CasResource cas = getCasResource(casSystemId);
+                if (cas == null) {
+                    throw new RemoteException("Releasing cas does not exist.");
+                }
+                if (!cas.getOwnerClientIds().contains(clientId)) {
+                    throw new RemoteException(
+                            "Client is not the current owner of the releasing cas.");
+                }
+                releaseCasSessionInternal(cas, clientId);
             }
         }
 
@@ -310,6 +344,9 @@
             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
                 throw new RemoteException("lnbHandle can't be invalid");
             }
+            if (!checkClientExists(clientId)) {
+                throw new RemoteException("Release lnb from unregistered client:" + clientId);
+            }
             int lnbId = getResourceIdFromHandle(lnbHandle);
             LnbResource lnb = getLnbResource(lnbId);
             if (lnb == null) {
@@ -465,17 +502,42 @@
     }
 
     @VisibleForTesting
-    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle)
-            throws RemoteException {
+    protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) {
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "updateCasInfo(casSystemId=" + casSystemId
+                            + ", maxSessionNum=" + maxSessionNum + ")");
+        }
+        // If maxSessionNum is 0, removing the Cas Resource.
+        if (maxSessionNum == 0) {
+            removeCasResource(casSystemId);
+            return;
+        }
+        // If the Cas exists, updates the Cas Resource accordingly.
+        CasResource cas = getCasResource(casSystemId);
+        if (cas != null) {
+            if (cas.getUsedSessionNum() > maxSessionNum) {
+                // Sort and release the short number of Cas resources.
+                int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum;
+                releaseLowerPriorityClientCasResources(releasingCasResourceNum);
+            }
+            cas.updateMaxSessionNum(maxSessionNum);
+            return;
+        }
+        // Add the new Cas Resource.
+        cas = new CasResource.Builder(casSystemId)
+                             .maxSessionNum(maxSessionNum)
+                             .build();
+        addCasResource(cas);
+    }
+
+    @VisibleForTesting
+    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestFrontend(request=" + request + ")");
         }
 
         frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        if (!checkClientExists(request.getClientId())) {
-            Slog.e(TAG, "Request frontend from unregistered client:" + request.getClientId());
-            return false;
-        }
         ClientProfile requestClient = getClientProfile(request.getClientId());
         int grantingFrontendId = -1;
         int inUseLowestPriorityFrId = -1;
@@ -496,7 +558,7 @@
                 } else if (grantingFrontendId < 0) {
                     // Record the frontend id with the lowest client priority among all the
                     // in use frontends when no available frontend has been found.
-                    int priority = getOwnerClientPriority(fr);
+                    int priority = getOwnerClientPriority(fr.getOwnerClientId());
                     if (currentLowestPriority > priority) {
                         inUseLowestPriorityFrId = fr.getId();
                         currentLowestPriority = priority;
@@ -530,17 +592,12 @@
     }
 
     @VisibleForTesting
-    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)
-            throws RemoteException {
+    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestLnb(request=" + request + ")");
         }
 
         lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        if (!checkClientExists(request.getClientId())) {
-            Slog.e(TAG, "Request lnb from unregistered client:" + request.getClientId());
-            return false;
-        }
         ClientProfile requestClient = getClientProfile(request.getClientId());
         int grantingLnbId = -1;
         int inUseLowestPriorityLnbId = -1;
@@ -554,7 +611,7 @@
             } else {
                 // Record the lnb id with the lowest client priority among all the
                 // in use lnb when no available lnb has been found.
-                int priority = getOwnerClientPriority(lnb);
+                int priority = getOwnerClientPriority(lnb.getOwnerClientId());
                 if (currentLowestPriority > priority) {
                     inUseLowestPriorityLnbId = lnb.getId();
                     currentLowestPriority = priority;
@@ -588,7 +645,55 @@
     }
 
     @VisibleForTesting
-    void releaseFrontendInternal(FrontendResource fe) {
+    protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestCasSession(request=" + request + ")");
+        }
+        CasResource cas = getCasResource(request.getCasSystemId());
+        // Unregistered Cas System is treated as having unlimited sessions.
+        if (cas == null) {
+            cas = new CasResource.Builder(request.getCasSystemId())
+                                 .maxSessionNum(Integer.MAX_VALUE)
+                                 .build();
+            addCasResource(cas);
+        }
+        casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        ClientProfile requestClient = getClientProfile(request.getClientId());
+        int lowestPriorityOwnerId = -1;
+        // Priority max value is 1000
+        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        if (!cas.isFullyUsed()) {
+            casSessionHandle[0] = generateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
+            updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId());
+            return true;
+        }
+        for (int ownerId : cas.getOwnerClientIds()) {
+            // Record the client id with lowest priority that is using the current Cas system.
+            int priority = getOwnerClientPriority(ownerId);
+            if (currentLowestPriority > priority) {
+                lowestPriorityOwnerId = ownerId;
+                currentLowestPriority = priority;
+            }
+        }
+
+        // When all the Cas sessions are occupied, reclaim the lowest priority client if the
+        // request client has higher priority.
+        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+            if (!reclaimResource(lowestPriorityOwnerId,
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
+                return false;
+            }
+            casSessionHandle[0] = generateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
+            updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId());
+            return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    protected void releaseFrontendInternal(FrontendResource fe) {
         if (DEBUG) {
             Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")");
         }
@@ -596,7 +701,7 @@
     }
 
     @VisibleForTesting
-    void releaseLnbInternal(LnbResource lnb) {
+    protected void releaseLnbInternal(LnbResource lnb) {
         if (DEBUG) {
             Slog.d(TAG, "releaseLnb(lnbId=" + lnb.getId() + ")");
         }
@@ -604,7 +709,15 @@
     }
 
     @VisibleForTesting
-    boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
+    protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) {
+        if (DEBUG) {
+            Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")");
+        }
+        updateCasClientMappingOnRelease(cas, ownerClientId);
+    }
+
+    @VisibleForTesting
+    protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestDemux(request=" + request + ")");
         }
@@ -614,7 +727,8 @@
     }
 
     @VisibleForTesting
-    boolean requestDescramblerInternal(TunerDescramblerRequest request, int[] descramblerHandle) {
+    protected boolean requestDescramblerInternal(
+            TunerDescramblerRequest request, int[] descramblerHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestDescrambler(request=" + request + ")");
         }
@@ -742,14 +856,28 @@
         ownerProfile.releaseLnb(releasingLnb.getId());
     }
 
+    private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) {
+        CasResource grantingCas = getCasResource(grantingId);
+        ClientProfile ownerProfile = getClientProfile(ownerClientId);
+        grantingCas.setOwner(ownerClientId);
+        ownerProfile.useCas(grantingId);
+    }
+
+    private void updateCasClientMappingOnRelease(
+            @NonNull CasResource releasingCas, int ownerClientId) {
+        ClientProfile ownerProfile = getClientProfile(ownerClientId);
+        releasingCas.removeOwner(ownerClientId);
+        ownerProfile.releaseCas();
+    }
+
     /**
      * Get the owner client's priority from the resource id.
      *
-     * @param resource a in use tuner resource.
+     * @param clientId the owner client id.
      * @return the priority of the owner client of the resource.
      */
-    private int getOwnerClientPriority(TunerResourceBasic resource) {
-        return getClientProfile(resource.getOwnerClientId()).getPriority();
+    private int getOwnerClientPriority(int clientId) {
+        return getClientProfile(clientId).getPriority();
     }
 
     @VisibleForTesting
@@ -783,6 +911,9 @@
 
     private void removeFrontendResource(int removingId) {
         FrontendResource fe = getFrontendResource(removingId);
+        if (fe == null) {
+            return;
+        }
         if (fe.isInUse()) {
             releaseFrontendInternal(fe);
         }
@@ -811,6 +942,9 @@
 
     private void removeLnbResource(int removingId) {
         LnbResource lnb = getLnbResource(removingId);
+        if (lnb == null) {
+            return;
+        }
         if (lnb.isInUse()) {
             releaseLnbInternal(lnb);
         }
@@ -819,6 +953,39 @@
 
     @VisibleForTesting
     @Nullable
+    protected CasResource getCasResource(int systemId) {
+        return mCasResources.get(systemId);
+    }
+
+    @VisibleForTesting
+    protected Map<Integer, CasResource> getCasResources() {
+        return mCasResources;
+    }
+
+    private void addCasResource(CasResource newCas) {
+        // Update resource list and available id list
+        mCasResources.put(newCas.getSystemId(), newCas);
+    }
+
+    private void removeCasResource(int removingId) {
+        CasResource cas = getCasResource(removingId);
+        if (cas == null) {
+            return;
+        }
+        for (int ownerId : cas.getOwnerClientIds()) {
+            getClientProfile(ownerId).releaseCas();
+        }
+        mCasResources.remove(removingId);
+    }
+
+    private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) {
+        // TODO: Sort with a treemap
+
+        // select the first num client to release
+    }
+
+    @VisibleForTesting
+    @Nullable
     protected ClientProfile getClientProfile(int clientId) {
         return mClientProfiles.get(clientId);
     }
@@ -830,12 +997,7 @@
     }
 
     private void removeClientProfile(int clientId) {
-        for (int id : getClientProfile(clientId).getInUseFrontendIds()) {
-            getFrontendResource(id).removeOwner();
-            for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) {
-                getFrontendResource(groupMemberId).removeOwner();
-            }
-        }
+        reclaimingResourcesFromClient(getClientProfile(clientId));
         mClientProfiles.remove(clientId);
         mListeners.remove(clientId);
     }
@@ -847,6 +1009,9 @@
         for (Integer lnbId : profile.getInUseLnbIds()) {
             getLnbResource(lnbId).removeOwner();
         }
+        if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
+            getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
+        }
         profile.reclaimAllResources();
     }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3f9f95c..abccf99 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1245,6 +1245,16 @@
                     }
                 }
 
+                for (int i = dc.mShellRoots.size() - 1; i >= 0; --i) {
+                    final WindowInfo info = dc.mShellRoots.valueAt(i).getWindowInfo();
+                    if (info == null) {
+                        continue;
+                    }
+                    info.layer = addedWindows.size();
+                    windows.add(info);
+                    addedWindows.add(info.token);
+                }
+
                 // Remove child/parent references to windows that were not added.
                 final int windowCount = windows.size();
                 for (int i = 0; i < windowCount; i++) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e76eda0..2648c86 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4712,7 +4712,7 @@
      */
     private boolean shouldBeResumed(ActivityRecord activeActivity) {
         return shouldMakeActive(activeActivity) && isFocusable()
-                && getRootTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
+                && getTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
                 && canResumeByCompat();
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 0f57496..ff43e77 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -18,11 +18,8 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -142,13 +139,11 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.SurfaceControl;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -157,7 +152,6 @@
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
@@ -276,11 +270,6 @@
 
     Rect mPreAnimationBounds = new Rect();
 
-    /**
-     * For {@link #prepareSurfaces}.
-     */
-    private final Point mLastSurfaceSize = new Point();
-
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -606,10 +595,6 @@
 
         super.onConfigurationChanged(newParentConfig);
 
-        // Only need to update surface size here since the super method will handle updating
-        // surface position.
-        updateSurfaceSize(getPendingTransaction());
-
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
         if (taskDisplayArea == null) {
             return;
@@ -3262,61 +3247,14 @@
         scheduleAnimation();
     }
 
-    /**
-     * Calculate an amount by which to expand the stack bounds in each direction.
-     * Used to make room for shadows in the pinned windowing mode.
-     */
-    int getStackOutset() {
-        // If we are drawing shadows on the task then don't outset the stack.
-        if (mWmService.mRenderShadowsInCompositor) {
-            return 0;
-        }
-        DisplayContent displayContent = getDisplayContent();
-        if (inPinnedWindowingMode() && displayContent != null) {
-            final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
-
-            // We multiply by two to match the client logic for converting view elevation
-            // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets}
-            return (int) Math.ceil(
-                    mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics)
-                            * 2);
-        }
-        return 0;
-    }
-
     @Override
     void getRelativePosition(Point outPos) {
         super.getRelativePosition(outPos);
-        final int outset = getStackOutset();
+        final int outset = getTaskOutset();
         outPos.x -= outset;
         outPos.y -= outset;
     }
 
-    private void updateSurfaceSize(SurfaceControl.Transaction transaction) {
-        if (mSurfaceControl == null) {
-            return;
-        }
-
-        final Rect stackBounds = getBounds();
-        int width = stackBounds.width();
-        int height = stackBounds.height();
-
-        final int outset = getStackOutset();
-        width += 2 * outset;
-        height += 2 * outset;
-
-        if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
-            return;
-        }
-        transaction.setWindowCrop(mSurfaceControl, width, height);
-        mLastSurfaceSize.set(width, height);
-    }
-
-    @VisibleForTesting
-    Point getLastSurfaceSize() {
-        return mLastSurfaceSize;
-    }
-
     @Override
     void onDisplayChanged(DisplayContent dc) {
         super.onDisplayChanged(dc);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 736bc9f..93a7574 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5400,14 +5400,11 @@
         final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
                 && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
                 && config.navigation == Configuration.NAVIGATION_NONAV);
-        int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
-        final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
-                && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
-                && modeType != Configuration.UI_MODE_TYPE_TELEVISION
-                && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
         final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(),
                 HIDE_ERROR_DIALOGS, 0) != 0;
-        mShowDialogs = inputMethodExists && uiModeSupportsDialogs && !hideDialogsSet;
+        mShowDialogs = inputMethodExists
+                && ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext)
+                && !hideDialogsSet;
     }
 
     private void updateFontScaleIfNeeded(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b01acca..e261632 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -26,7 +26,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -574,7 +573,7 @@
     /** Corner radius that windows should have in order to match the display. */
     private final float mWindowCornerRadius;
 
-    private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+    final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
     RemoteInsetsControlTarget mRemoteInsetsControlTarget = null;
     private final IBinder.DeathRecipient mRemoteInsetsDeath =
             () -> {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e9d3d56..8aace21 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -261,6 +261,8 @@
     @Px
     private int mRightGestureInset;
 
+    private boolean mNavButtonForcedVisible;
+
     StatusBarManagerInternal getStatusBarManagerInternal() {
         synchronized (mServiceAcquireLock) {
             if (mStatusBarManagerInternal == null) {
@@ -1046,12 +1048,14 @@
                             // calculate inset.
                             if (navigationBarPosition(displayFrames.mDisplayWidth,
                                     displayFrames.mDisplayHeight,
-                                    displayFrames.mRotation) == NAV_BAR_BOTTOM) {
+                                    displayFrames.mRotation) == NAV_BAR_BOTTOM
+                                    && !mNavButtonForcedVisible) {
+
                                 sTmpRect.set(displayFrames.mUnrestricted);
                                 sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
                                 inOutFrame.top = sTmpRect.bottom
                                         - getNavigationBarHeight(displayFrames.mRotation,
-                                                mDisplayContent.getConfiguration().uiMode);
+                                        mDisplayContent.getConfiguration().uiMode);
                             }
                         },
 
@@ -2810,6 +2814,8 @@
         mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
         mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
         mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+        mNavButtonForcedVisible =
+                mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
         mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
         mNavigationBarAlwaysShowOnSideGesture =
                 res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
@@ -3783,13 +3789,14 @@
      * @param screenshotType The type of screenshot, for example either
      *                       {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or
      *                       {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
+     * @param source Where the screenshot originated from (see WindowManager.ScreenshotSource)
      */
-    public void takeScreenshot(int screenshotType) {
+    public void takeScreenshot(int screenshotType, int source) {
         if (mScreenshotHelper != null) {
             mScreenshotHelper.takeScreenshot(screenshotType,
                     mStatusBar != null && mStatusBar.isVisibleLw(),
                     mNavigationBar != null && mNavigationBar.isVisibleLw(),
-                    mHandler, null /* completionConsumer */);
+                    source, mHandler, null /* completionConsumer */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 20738ed..803bec8 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -45,6 +45,11 @@
 
     void setTouchRegion(Rect touchRegion) {
         mTouchRegion.set(touchRegion);
+        // We need to report touchable region changes to accessibility.
+        if (mDisplayContent.mWmService.mAccessibilityController != null) {
+            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(
+                    mDisplayContent.getDisplayId());
+        }
     }
 
     void getTouchRegion(Rect outRegion) {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 5f33ea1..9d44cad 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -597,8 +597,8 @@
             return startAnimation(initializeBuilder()
                             .setSurfaceControl(mScreenshotLayer)
                             .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
-                            .setWidth(mWidth)
-                            .setHeight(mHeight)
+                            .setWidth(mDisplayContent.getSurfaceWidth())
+                            .setHeight(mDisplayContent.getSurfaceHeight())
                             .build(),
                     createWindowAnimationSpec(mRotateAlphaAnimation),
                     this::onAnimationEnd);
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 701feff..0b1760d 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -23,12 +23,14 @@
 
 import android.annotation.NonNull;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.IWindow;
 import android.view.SurfaceControl;
+import android.view.WindowInfo;
 import android.view.animation.Animation;
 
 /**
@@ -102,5 +104,27 @@
         mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */,
                 ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */);
     }
+
+    WindowInfo getWindowInfo() {
+        if (mToken.windowType != TYPE_DOCK_DIVIDER) {
+            return null;
+        }
+        if (!mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
+            return null;
+        }
+        WindowInfo windowInfo = WindowInfo.obtain();
+        windowInfo.displayId = mToken.getDisplayArea().getDisplayContent().mDisplayId;
+        windowInfo.type = mToken.windowType;
+        windowInfo.layer = mToken.getWindowLayerFromType();
+        windowInfo.token = mClient.asBinder();
+        windowInfo.title = "Splitscreen Divider";
+        windowInfo.focused = false;
+        windowInfo.inPictureInPicture = false;
+        windowInfo.hasFlagWatchOutsideTouch = false;
+        final Rect regionRect = new Rect();
+        mDisplayContent.getDockedDividerController().getTouchRegion(regionRect);
+        windowInfo.regionInScreen.set(regionRect);
+        return windowInfo;
+    }
 }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 66ca0ac..df5cfee 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -120,6 +120,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
@@ -213,7 +214,6 @@
 
     static final int INVALID_MIN_SIZE = -1;
     private float mShadowRadius = 0;
-    private final Rect mLastSurfaceCrop = new Rect();
 
     /**
      * The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
@@ -397,6 +397,7 @@
 
     private Dimmer mDimmer = new Dimmer(this);
     private final Rect mTmpDimBoundsRect = new Rect();
+    private final Point mLastSurfaceSize = new Point();
 
     /** @see #setCanAffectSystemUiFlags */
     private boolean mCanAffectSystemUiFlags = true;
@@ -1943,6 +1944,10 @@
         mTmpPrevBounds.set(getBounds());
         final boolean wasInMultiWindowMode = inMultiWindowMode();
         super.onConfigurationChanged(newParentConfig);
+        // Only need to update surface size here since the super method will handle updating
+        // surface position.
+        updateSurfaceSize(getPendingTransaction());
+
         if (wasInMultiWindowMode != inMultiWindowMode()) {
             mStackSupervisor.scheduleUpdateMultiWindowMode(this);
         }
@@ -1995,6 +2000,57 @@
         return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
     }
 
+    void updateSurfaceSize(SurfaceControl.Transaction transaction) {
+        if (mSurfaceControl == null || mCreatedByOrganizer) {
+            return;
+        }
+
+        // Apply crop to root tasks only and clear the crops of the descendant tasks.
+        int width = 0;
+        int height = 0;
+        if (isRootTask()) {
+            final Rect taskBounds = getBounds();
+            width = taskBounds.width();
+            height = taskBounds.height();
+
+            final int outset = getTaskOutset();
+            width += 2 * outset;
+            height += 2 * outset;
+        }
+        if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+            return;
+        }
+        transaction.setWindowCrop(mSurfaceControl, width, height);
+        mLastSurfaceSize.set(width, height);
+    }
+
+    /**
+     * Calculate an amount by which to expand the task bounds in each direction.
+     * Used to make room for shadows in the pinned windowing mode.
+     */
+    int getTaskOutset() {
+        // If we are drawing shadows on the task then don't outset the stack.
+        if (mWmService.mRenderShadowsInCompositor) {
+            return 0;
+        }
+        DisplayContent displayContent = getDisplayContent();
+        if (inPinnedWindowingMode() && displayContent != null) {
+            final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
+
+            // We multiply by two to match the client logic for converting view elevation
+            // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets}
+            return (int) Math.ceil(
+                    mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics)
+                            * 2);
+        }
+        return 0;
+    }
+
+    @VisibleForTesting
+    Point getLastSurfaceSize() {
+        return mLastSurfaceSize;
+    }
+
     @VisibleForTesting
     boolean isInChangeTransition() {
         return mSurfaceFreezer.hasLeash() || AppTransition.isChangeTransit(mTransit);
@@ -2225,14 +2281,16 @@
         }
         density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
 
+        // If bounds have been overridden at this level, restrict config resources to these bounds
+        // rather than the parent because the overridden bounds can be larger than the parent.
+        boolean hasOverrideBounds = false;
+
         final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
-        if (resolvedBounds == null) {
-            mTmpFullBounds.setEmpty();
+        if (resolvedBounds == null || resolvedBounds.isEmpty()) {
+            mTmpFullBounds.set(parentConfig.windowConfiguration.getBounds());
         } else {
             mTmpFullBounds.set(resolvedBounds);
-        }
-        if (mTmpFullBounds.isEmpty()) {
-            mTmpFullBounds.set(parentConfig.windowConfiguration.getBounds());
+            hasOverrideBounds = true;
         }
 
         Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -2244,7 +2302,16 @@
         // the out bounds doesn't need to be restricted by the parent.
         final boolean insideParentBounds = compatInsets == null;
         if (insideParentBounds && windowingMode != WINDOWING_MODE_FREEFORM) {
-            final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+            Rect parentAppBounds;
+            if (hasOverrideBounds) {
+                // Since we overrode the bounds, restrict appBounds to display non-decor rather
+                // than parent. Otherwise, it won't match the overridden bounds.
+                final TaskDisplayArea displayArea = getDisplayArea();
+                parentAppBounds = displayArea != null
+                        ? displayArea.getConfiguration().windowConfiguration.getAppBounds() : null;
+            } else {
+                parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+            }
             if (parentAppBounds != null && !parentAppBounds.isEmpty()) {
                 outAppBounds.intersect(parentAppBounds);
             }
@@ -2291,13 +2358,13 @@
 
             if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
                 final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
-                inOutConfig.screenWidthDp = insideParentBounds
+                inOutConfig.screenWidthDp = (insideParentBounds && !hasOverrideBounds)
                         ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
                         : overrideScreenWidthDp;
             }
             if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
                 final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
-                inOutConfig.screenHeightDp = insideParentBounds
+                inOutConfig.screenHeightDp = (insideParentBounds && !hasOverrideBounds)
                         ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
                         : overrideScreenHeightDp;
             }
@@ -2344,27 +2411,27 @@
         mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
         super.resolveOverrideConfiguration(newParentConfig);
 
-        // Resolve override windowing mode to fullscreen for home task (even on freeform
-        // display), or split-screen-secondary if in split-screen mode.
         int windowingMode =
                 getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+
+        // Resolve override windowing mode to fullscreen for home task (even on freeform
+        // display), or split-screen if in split-screen mode.
         if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
             final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
-            windowingMode = parentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                    ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                    : WINDOWING_MODE_FULLSCREEN;
+            windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
+                    ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
             getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
         }
 
-        if (!isLeafTask()) {
-            // Compute configuration overrides for tasks that created by organizer, so that
-            // organizer can get the correct configuration from those tasks.
-            if (mCreatedByOrganizer) {
-                computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
-            }
-            return;
+        if (isLeafTask()) {
+            resolveLeafOnlyOverrideConfigs(newParentConfig);
         }
+        computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+    }
 
+    void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig) {
+        int windowingMode =
+                getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
         if (windowingMode == WINDOWING_MODE_UNDEFINED) {
             windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
         }
@@ -2404,7 +2471,6 @@
                 outOverrideBounds.offset(0, offsetTop);
             }
         }
-        computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
     }
 
     /**
@@ -2803,28 +2869,6 @@
         return boundsChange;
     }
 
-    private void updateSurfaceCrop() {
-        // Only update the crop if we are drawing shadows on the task.
-        if (mSurfaceControl == null || !mWmService.mRenderShadowsInCompositor || !isRootTask()) {
-            return;
-        }
-
-        if (inSplitScreenWindowingMode()) {
-            // inherit crop from parent
-            mTmpRect.setEmpty();
-        } else {
-            getBounds(mTmpRect);
-        }
-
-        mTmpRect.offsetTo(0, 0);
-        if (mLastSurfaceCrop.equals(mTmpRect)) {
-            return;
-        }
-
-        getPendingTransaction().setWindowCrop(mSurfaceControl, mTmpRect);
-        mLastSurfaceCrop.set(mTmpRect);
-    }
-
     @Override
     public boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
             ConfigurationContainer requestingContainer) {
@@ -3453,7 +3497,6 @@
             mTmpDimBoundsRect.offsetTo(0, 0);
         }
 
-        updateSurfaceCrop();
         updateShadowsRadius(isFocused(), getPendingTransaction());
 
         if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a501414..51095ee 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2446,17 +2446,15 @@
             if (controls != null) {
                 final int length = Math.min(controls.length, outControls.length);
                 for (int i = 0; i < length; i++) {
-                    final InsetsSourceControl control = controls[i];
-
-                    // Check if we are sending invalid leashes.
-                    final SurfaceControl leash = control != null ? control.getLeash() : null;
-                    if (leash != null && !leash.isValid()) {
-                        Slog.wtf(TAG, leash + " is not valid before sending to " + win,
-                                leash.getReleaseStack());
-                    }
-
-                    outControls[i] = win.isClientLocal() && control != null
-                            ? new InsetsSourceControl(control) : control;
+                    // We will leave the critical section before returning the leash to the client,
+                    // so we need to copy the leash to prevent others release the one that we are
+                    // about to return.
+                    // TODO: We will have an extra copy if the client is not local.
+                    //       For now, we rely on GC to release it.
+                    //       Maybe we can modify InsetsSourceControl.writeToParcel so it can release
+                    //       the extra leash as soon as possible.
+                    outControls[i] = controls[i] != null
+                            ? new InsetsSourceControl(controls[i]) : null;
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 83e7ad5..ef690e1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5289,7 +5289,7 @@
         // to account for it. If we actually have shadows we will
         // then un-inset ourselves by the surfaceInsets.
         if (stack != null) {
-            final int outset = stack.getStackOutset();
+            final int outset = stack.getTaskOutset();
             outPoint.offset(outset, outset);
         }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2c0d4c0..7b84555 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -161,8 +161,11 @@
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IntentSender;
 import android.content.PermissionChecker;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -171,6 +174,7 @@
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -2703,8 +2707,9 @@
         final ComponentName doAdminReceiver = doAdmin.info.getComponent();
         clearDeviceOwnerLocked(doAdmin, doUserId);
         Slog.i(LOG_TAG, "Removing admin artifacts...");
-        // TODO(b/149075700): Clean up application restrictions in UserManager.
         removeAdminArtifacts(doAdminReceiver, doUserId);
+        Slog.i(LOG_TAG, "Uninstalling the DO...");
+        uninstallOrDisablePackage(doAdminComponent.getPackageName(), doUserId);
         Slog.i(LOG_TAG, "Migration complete.");
 
         // Note: KeyChain keys are not removed and will remain accessible for the apps that have
@@ -2716,6 +2721,47 @@
                 .write();
     }
 
+    private void uninstallOrDisablePackage(String packageName, int userHandle) {
+        final ApplicationInfo appInfo;
+        try {
+            appInfo = mIPackageManager.getApplicationInfo(
+                    packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+        } catch (RemoteException e) {
+            // Shouldn't happen.
+            return;
+        }
+        if (appInfo == null) {
+            Slog.wtf(LOG_TAG, "Failed to get package info for " + packageName);
+            return;
+        }
+        if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            Slog.i(LOG_TAG, String.format(
+                    "Package %s is pre-installed, marking disabled until used", packageName));
+            mContext.getPackageManager().setApplicationEnabledSetting(packageName,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0 /* flags */);
+            return;
+        }
+
+        final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                final int status = intent.getIntExtra(
+                        PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    Slog.i(LOG_TAG, String.format(
+                            "Package %s uninstalled for user %d", packageName, userHandle));
+                } else {
+                    Slog.e(LOG_TAG, String.format(
+                            "Failed to uninstall %s; status: %d", packageName, status));
+                }
+            }
+        };
+
+        final PackageInstaller pi = mInjector.getPackageManager(userHandle).getPackageInstaller();
+        pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender));
+    }
+
     private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
         // The following policies can be already controlled via parent instance, skip if so.
         if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
@@ -8766,6 +8812,8 @@
         saveSettingsLocked(UserHandle.USER_SYSTEM);
         clearUserPoliciesLocked(userId);
         clearOverrideApnUnchecked();
+        clearApplicationRestrictions(userId);
+        mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId);
 
         mOwners.clearDeviceOwner();
         mOwners.writeDeviceOwner();
@@ -8779,6 +8827,19 @@
         toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
     }
 
+    private void clearApplicationRestrictions(int userId) {
+        // Changing app restrictions involves disk IO, offload it to the background thread.
+        mBackgroundHandler.post(() -> {
+            final List<PackageInfo> installedPackageInfos = mInjector.getPackageManager(userId)
+                    .getInstalledPackages(MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
+            final UserHandle userHandle = UserHandle.of(userId);
+            for (final PackageInfo packageInfo : installedPackageInfos) {
+                mInjector.getUserManager().setApplicationRestrictions(
+                        packageInfo.packageName, null /* restrictions */, userHandle);
+            }
+        });
+    }
+
     @Override
     public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
         if (!mHasFeature) {
@@ -8898,6 +8959,7 @@
         policyData.mOwnerInstalledCaCerts.clear();
         saveSettingsLocked(userId);
         clearUserPoliciesLocked(userId);
+        clearApplicationRestrictions(userId);
         mOwners.removeProfileOwner(userId);
         mOwners.writeProfileOwner(userId);
         deleteTransferOwnershipBundleLocked(userId);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index c87ece2..763e19b 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -67,6 +67,7 @@
 import com.android.internal.telephony.SmsApplication;
 import com.android.server.LocalServices;
 import com.android.server.notification.NotificationManagerInternal;
+import com.android.server.notification.ShortcutHelper;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -497,10 +498,6 @@
                 EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
     }
 
-    private boolean isPersonShortcut(@NonNull ShortcutInfo shortcutInfo) {
-        return shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0;
-    }
-
     @VisibleForTesting
     @WorkerThread
     void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
@@ -712,7 +709,8 @@
                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
             mInjector.getBackgroundExecutor().execute(() -> {
                 for (ShortcutInfo shortcut : shortcuts) {
-                    if (isPersonShortcut(shortcut)) {
+                    if (ShortcutHelper.isConversationShortcut(
+                            shortcut, mShortcutServiceInternal, user.getIdentifier())) {
                         addOrUpdateConversationInfo(shortcut);
                     }
                 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 3352177..064e348 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
@@ -308,7 +310,7 @@
                 AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
         verify(mMockScreenshotHelper).takeScreenshot(
                 eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
-                anyBoolean(), any(Handler.class), any());
+                anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
     }
 
     // PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 74e7f8c..a0b9d9d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -62,6 +62,10 @@
 
         mContext = getContext();
 
+        // Make createContextAsUser to work.
+        mContext.packageName = "com.android.frameworks.servicestests";
+        getServices().addPackageContext(UserHandle.of(0), mContext);
+
         when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
                 .thenReturn(true);
     }
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 09d1d3a..57039e5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -196,6 +196,11 @@
                         anyInt(),
                         any(UserHandle.class));
 
+        // Make createContextAsUser to work.
+        mContext.packageName = "com.android.frameworks.servicestests";
+        getServices().addPackageContext(UserHandle.of(0), mContext);
+        getServices().addPackageContext(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE), mContext);
+
         // By default, pretend all users are running and unlocked.
         when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true);
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 8625a1e..20716ab 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -460,6 +460,15 @@
     }
 
     @Override
+    public Context createContextAsUser(UserHandle user, int flags) {
+        try {
+            return mMockSystemServices.createPackageContextAsUser(packageName, flags, user);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
     public ContentResolver getContentResolver() {
         return mMockSystemServices.contentResolver;
     }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 728e149..e16f314 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -215,6 +215,8 @@
         mDataManager = new DataManager(mContext, mInjector);
         mDataManager.initialize();
 
+        when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
+                anyString(), anyInt(), any())).thenReturn(true);
         verify(mShortcutServiceInternal).addShortcutChangeCallback(
                 mShortcutChangeCallbackCaptor.capture());
         mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 965304f..21af356 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -26,6 +26,7 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvInputService;
 import android.media.tv.tuner.frontend.FrontendSettings;
+import android.media.tv.tunerresourcemanager.CasSessionRequest;
 import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
@@ -34,7 +35,6 @@
 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
 import android.media.tv.tunerresourcemanager.TunerLnbRequest;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
-import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -236,12 +236,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
@@ -264,12 +260,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
@@ -296,12 +288,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(0);
     }
@@ -334,23 +322,15 @@
         int[] frontendHandle = new int[1];
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[0].getId());
 
         request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
@@ -394,32 +374,20 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-            assertThat(listener.isRelaimed()).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
+        assertThat(listener.isRelaimed()).isFalse();
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-            assertThat(listener.isRelaimed()).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
+        assertThat(listener.isRelaimed()).isFalse();
     }
 
     @Test
@@ -456,12 +424,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
@@ -470,12 +434,8 @@
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
@@ -511,12 +471,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         int frontendId = mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]);
         assertThat(frontendId).isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService
@@ -534,6 +490,99 @@
     }
 
     @Test
+    public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() {
+        // Register clients
+        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
+        profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        profiles[1] = new ResourceClientProfile("1" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientPriorities = {100, 500};
+        int[] clientId0 = new int[1];
+        int[] clientId1 = new int[1];
+        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profiles[0], listener, clientId0);
+        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profiles[1], new TestResourcesReclaimListener(), clientId1);
+        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.getClientProfile(clientId1[0])
+                .setPriority(clientPriorities[1]);
+
+        // Init cas resources.
+        mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+        CasSessionRequest request = new CasSessionRequest(clientId0[0], 1 /*casSystemId*/);
+        int[] casSessionHandle = new int[1];
+        // Request for 2 cas sessions.
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+                .isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0])));
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
+
+        request = new CasSessionRequest(clientId1[0], 1);
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+                .isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0])
+                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+        assertThat(listener.isRelaimed()).isTrue();
+    }
+
+    @Test
+    public void releaseCasTest() {
+        // Register clients
+        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
+        profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientId = new int[1];
+        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
+        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        // Init cas resources.
+        mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+        CasSessionRequest request = new CasSessionRequest(clientId[0], 1 /*casSystemId*/);
+        int[] casSessionHandle = new int[1];
+        // Request for 1 cas sessions.
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+                .isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
+                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0])));
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+
+        // Release cas
+        mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService
+                .getCasResource(1), clientId[0]);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
+                .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEmpty();
+    }
+
+    @Test
     public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() {
         // Register clients
         ResourceClientProfile[] profiles = new ResourceClientProfile[2];
@@ -562,24 +611,16 @@
 
         TunerLnbRequest request = new TunerLnbRequest(clientId0[0]);
         int[] lnbHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestLnbInternal(request, lnbHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
                 .isEqualTo(lnbIds[0]);
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
                 .getInUseLnbIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(lnbIds[0])));
 
         request = new TunerLnbRequest(clientId1[0]);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestLnbInternal(request, lnbHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
                 .isEqualTo(lnbIds[0]);
         assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0])
@@ -608,12 +649,8 @@
 
         TunerLnbRequest request = new TunerLnbRequest(clientId[0]);
         int[] lnbHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestLnbInternal(request, lnbHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(request, lnbHandle)).isTrue();
         int lnbId = mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]);
         assertThat(lnbId).isEqualTo(lnbIds[0]);
 
@@ -647,12 +684,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
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 ecdd9e5..7a5e226 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6074,6 +6074,9 @@
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
         ShortcutInfo info = mock(ShortcutInfo.class);
+        when(info.getPackage()).thenReturn(PKG);
+        when(info.getId()).thenReturn("someshortcutId");
+        when(info.getUserId()).thenReturn(USER_SYSTEM);
         when(info.isLongLived()).thenReturn(true);
         when(info.isEnabled()).thenReturn(true);
         shortcutInfos.add(info);
@@ -6137,6 +6140,9 @@
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
         ShortcutInfo info = mock(ShortcutInfo.class);
+        when(info.getPackage()).thenReturn(PKG);
+        when(info.getId()).thenReturn("someshortcutId");
+        when(info.getUserId()).thenReturn(USER_SYSTEM);
         when(info.isLongLived()).thenReturn(true);
         when(info.isEnabled()).thenReturn(true);
         shortcutInfos.add(info);
@@ -6483,6 +6489,9 @@
         when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
 
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG_P);
+        when(si.getId()).thenReturn("convo");
+        when(si.getUserId()).thenReturn(USER_SYSTEM);
         when(si.getShortLabel()).thenReturn("Hello");
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(true);
@@ -6514,6 +6523,9 @@
         when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
 
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG_P);
+        when(si.getId()).thenReturn("convo");
+        when(si.getUserId()).thenReturn(USER_SYSTEM);
         when(si.getShortLabel()).thenReturn("Hello");
         when(si.isLongLived()).thenReturn(false);
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index f7304bd..3095c87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -170,6 +170,9 @@
     @Test
     public void testGetValidShortcutInfo_notLongLived() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(false);
         when(si.isEnabled()).thenReturn(true);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -184,6 +187,9 @@
     @Test
     public void testGetValidShortcutInfo_notSharingShortcut() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(true);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -198,6 +204,9 @@
     @Test
     public void testGetValidShortcutInfo_notEnabled() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(false);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -212,6 +221,9 @@
     @Test
     public void testGetValidShortcutInfo_isValid() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(true);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 6ae8313..0700f9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -329,6 +329,7 @@
         private boolean mCreateStack = true;
 
         private ActivityStack mStack;
+        private TaskDisplayArea mTaskDisplayArea;
 
         TaskBuilder(ActivityStackSupervisor supervisor) {
             mSupervisor = supervisor;
@@ -378,9 +379,16 @@
             return this;
         }
 
+        TaskBuilder setDisplay(DisplayContent display) {
+            mTaskDisplayArea = display.getDefaultTaskDisplayArea();
+            return this;
+        }
+
         Task build() {
             if (mStack == null && mCreateStack) {
-                mStack = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
+                TaskDisplayArea displayArea = mTaskDisplayArea != null ? mTaskDisplayArea
+                        : mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+                mStack = displayArea.createStack(
                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
                 spyOn(mStack);
             }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index be25597..e887be0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -33,6 +33,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Build/Install/Run:
@@ -40,6 +41,7 @@
  */
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 @FlakyTest
 public class RefreshRatePolicyTest extends WindowTestsBase {
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 519ac78..dcc2ff1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -54,10 +54,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
 import android.app.TaskInfo;
@@ -75,12 +72,10 @@
 import android.util.Xml;
 import android.view.DisplayInfo;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import com.android.internal.app.IVoiceInteractor;
 import com.android.server.wm.Task.TaskFactory;
-import com.android.server.wm.utils.WmDisplayCutout;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -368,25 +363,38 @@
 
     @Test
     public void testComputeConfigResourceOverrides() {
-        final Task task = new TaskBuilder(mSupervisor).build();
+        final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920);
+        TestDisplayContent display = new TestDisplayContent.Builder(
+                mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
         final Configuration inOutConfig = new Configuration();
         final Configuration parentConfig = new Configuration();
         final int longSide = 1200;
         final int shortSide = 600;
+        final Rect parentBounds = new Rect(0, 0, 250, 500);
+        parentConfig.windowConfiguration.setBounds(parentBounds);
         parentConfig.densityDpi = 400;
-        parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px
-        parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
+        parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200
+        parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100
         parentConfig.windowConfiguration.setRotation(ROTATION_0);
 
-        // Portrait bounds.
-        inOutConfig.windowConfiguration.getBounds().set(0, 0, shortSide, longSide);
-        // By default, the parent bounds should limit the existing input bounds.
+        // By default, the input bounds will fill parent.
         task.computeConfigResourceOverrides(inOutConfig, parentConfig);
 
         assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
         assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
         assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation);
 
+        // If bounds are overridden, config properties should be made to match. Surface hierarchy
+        // will crop for policy.
+        inOutConfig.setToDefaults();
+        inOutConfig.windowConfiguration.getBounds().set(0, 0, shortSide, longSide);
+        // By default, the parent bounds should limit the existing input bounds.
+        task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+        assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160);
+        assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160);
+
         inOutConfig.setToDefaults();
         // Landscape bounds.
         inOutConfig.windowConfiguration.getBounds().set(0, 0, longSide, shortSide);
@@ -394,21 +402,17 @@
         // Setup the display with a top stable inset. The later assertion will ensure the inset is
         // excluded from screenHeightDp.
         final int statusBarHeight = 100;
-        final DisplayContent displayContent = task.mDisplayContent;
-        final DisplayPolicy policy = mock(DisplayPolicy.class);
+        final DisplayPolicy policy = display.getDisplayPolicy();
         doAnswer(invocationOnMock -> {
             final Rect insets = invocationOnMock.<Rect>getArgument(0);
             insets.top = statusBarHeight;
             return null;
         }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
-        doReturn(policy).when(displayContent).getDisplayPolicy();
-        doReturn(mock(WmDisplayCutout.class)).when(displayContent)
-                .calculateDisplayCutoutForRotation(anyInt());
 
         // Without limiting to be inside the parent bounds, the out screen size should keep relative
         // to the input bounds.
         final ActivityRecord.CompatDisplayInsets compatIntsets =
-                new ActivityRecord.CompatDisplayInsets(displayContent, task);
+                new ActivityRecord.CompatDisplayInsets(display, task);
         task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets);
 
         assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi,
@@ -454,7 +458,6 @@
         parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
         parentConfig.windowConfiguration.setRotation(ROTATION_0);
 
-        final float density = 2.5f; // densityDpi / DENSITY_DEFAULT_SCALE = 400 / 160.0f
         final int longSideDp = 480; // longSide / density = 1200 / 400 * 160
         final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160
         final int screenLayout = parentConfig.screenLayout
@@ -463,31 +466,38 @@
                 Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp);
 
         // Portrait bounds overlapping with navigation bar, without insets.
-        inOutConfig.windowConfiguration.getBounds().set(0,
+        final Rect freeformBounds = new Rect(0,
                 displayHeight - 10 - longSide,
                 shortSide,
                 displayHeight - 10);
+        inOutConfig.windowConfiguration.setBounds(freeformBounds);
         // Set to freeform mode to verify bug fix.
         inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
 
         task.computeConfigResourceOverrides(inOutConfig, parentConfig);
 
-        assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
-        assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+        // screenW/H should not be effected by parent since overridden and freeform
+        assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+                inOutConfig.screenWidthDp);
+        assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+                inOutConfig.screenHeightDp);
         assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
 
         inOutConfig.setToDefaults();
         // Landscape bounds overlapping with navigtion bar, without insets.
-        inOutConfig.windowConfiguration.getBounds().set(0,
+        freeformBounds.set(0,
                 displayHeight - 10 - shortSide,
                 longSide,
                 displayHeight - 10);
+        inOutConfig.windowConfiguration.setBounds(freeformBounds);
         inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
 
         task.computeConfigResourceOverrides(inOutConfig, parentConfig);
 
-        assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
-        assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+        assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+                inOutConfig.screenWidthDp);
+        assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+                inOutConfig.screenHeightDp);
         assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 7cb5e84..f354a04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -186,7 +186,7 @@
         final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
         final int stackOutset = 10;
         spyOn(stack);
-        doReturn(stackOutset).when(stack).getStackOutset();
+        doReturn(stackOutset).when(stack).getTaskOutset();
         doReturn(true).when(stack).inMultiWindowMode();
 
         // Mock the resolved override windowing mode to non-fullscreen
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 53cc09b..f65328d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -44,7 +44,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -407,22 +406,20 @@
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         final Task task = stack.getTopMostTask();
         WindowContainerTransaction t = new WindowContainerTransaction();
-        t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100));
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         final int origScreenWDp = task.getConfiguration().screenHeightDp;
         final int origScreenHDp = task.getConfiguration().screenHeightDp;
         t = new WindowContainerTransaction();
         // verify that setting config overrides on parent restricts children.
         t.setScreenSizeDp(stack.mRemoteToken
-                .toWindowContainerToken(), origScreenWDp, origScreenHDp);
-        t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 150, 200));
+                .toWindowContainerToken(), origScreenWDp, origScreenHDp / 2);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
+        assertEquals(origScreenHDp / 2, task.getConfiguration().screenHeightDp);
         t = new WindowContainerTransaction();
         t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED,
                 SCREEN_HEIGHT_DP_UNDEFINED);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        assertNotEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
+        assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
     }
 
     @Test
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 9dfa3ac..fa99095 100755
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -734,6 +734,31 @@
             "android.telecom.extra.ORIGINAL_CONNECTION_ID";
 
     /**
+     * Extra key set on a {@link Connection} when it was created via a remote connection service.
+     * For example, if a connection manager requests a remote connection service to create a call
+     * using one of the remote connection service's phone account handle, this extra will be set so
+     * that Telecom knows that the wrapped remote connection originated in a remote connection
+     * service.  We stash this in the extras since connection managers will typically copy the
+     * extras from a {@link RemoteConnection} to a {@link Connection} (there is ultimately not
+     * other way to relate a {@link RemoteConnection} to a {@link Connection}.
+     * @hide
+     */
+    public static final String EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE =
+            "android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE";
+
+    /**
+     * Extra key set from a {@link ConnectionService} when using the remote connection APIs
+     * (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle,
+     * ConnectionRequest, boolean)}) to create a remote connection.  Provides the receiving
+     * {@link ConnectionService} with a means to know the package name of the requesting
+     * {@link ConnectionService} so that {@link #EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE} can be set for
+     * better visibility in Telecom of where a connection ultimately originated.
+     * @hide
+     */
+    public static final String EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME =
+            "android.telecom.extra.REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME";
+
+    /**
      * Boolean connection extra key set on the extras passed to
      * {@link Connection#sendConnectionEvent} which indicates that audio is present
      * on the RTT call when the extra value is true.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 1b60e48..a716b37 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1859,9 +1859,25 @@
                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"),
                     request.getAccountHandle());
         }
-        if (conference.getExtras() != null) {
-            conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+
+        Bundle extras = request.getExtras();
+        Bundle newExtras = new Bundle();
+        newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+        if (extras != null) {
+            // If the request originated from a remote connection service, we will add some
+            // tracking information that Telecom can use to keep informed of which package
+            // made the remote request, and which remote connection service was used.
+            if (extras.containsKey(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) {
+                newExtras.putString(
+                        Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
+                        extras.getString(
+                                Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME));
+                newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+                        request.getAccountHandle());
+            }
         }
+        conference.putExtras(newExtras);
+
         mConferenceById.put(callId, conference);
         mIdByConference.put(conference, callId);
         conference.addListener(mConferenceListener);
@@ -1936,6 +1952,30 @@
             Log.i(this, "createConnection, implementation returned null connection.");
             connection = Connection.createFailedConnection(
                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
+        } else {
+            try {
+                Bundle extras = request.getExtras();
+                if (extras != null) {
+                    // If the request originated from a remote connection service, we will add some
+                    // tracking information that Telecom can use to keep informed of which package
+                    // made the remote request, and which remote connection service was used.
+                    if (extras.containsKey(
+                            Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) {
+                        Bundle newExtras = new Bundle();
+                        newExtras.putString(
+                                Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
+                                extras.getString(
+                                        Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME
+                                ));
+                        newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+                                request.getAccountHandle());
+                        connection.putExtras(newExtras);
+                    }
+                }
+            } catch (UnsupportedOperationException ose) {
+                // Do nothing; if the ConnectionService reported a failure it will be an instance
+                // of an immutable Connection which we cannot edit, so we're out of luck.
+            }
         }
 
         boolean isSelfManaged =
diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java
index d82e93f..8d3f4e1 100644
--- a/telecomm/java/android/telecom/Logging/Session.java
+++ b/telecomm/java/android/telecom/Logging/Session.java
@@ -427,7 +427,7 @@
             StringBuilder methodName = new StringBuilder();
             methodName.append(getFullMethodPath(false /*truncatePath*/));
             if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
-                methodName.append("(InCall package: ");
+                methodName.append("(");
                 methodName.append(mOwnerInfo);
                 methodName.append(")");
             }
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index cad5b70..a083301 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -258,6 +258,9 @@
             // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
             Bundle newExtras = new Bundle();
             newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+            // Track the fact this request was relayed through the remote connection service.
+            newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+                    parcel.getPhoneAccount());
             conference.putExtras(newExtras);
 
             conference.registerCallback(new RemoteConference.Callback() {
@@ -383,6 +386,11 @@
             RemoteConnection remoteConnection = new RemoteConnection(callId,
                     mOutgoingConnectionServiceRpc, connection, callingPackage,
                     callingTargetSdkVersion);
+            // Track that it is via a remote connection.
+            Bundle newExtras = new Bundle();
+            newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+                    connection.getPhoneAccount());
+            remoteConnection.putExtras(newExtras);
             mConnectionById.put(callId, remoteConnection);
             remoteConnection.registerCallback(new RemoteConnection.Callback() {
                 @Override
@@ -535,10 +543,20 @@
             ConnectionRequest request,
             boolean isIncoming) {
         final String id = UUID.randomUUID().toString();
+        Bundle extras = new Bundle();
+        if (request.getExtras() != null) {
+            extras.putAll(request.getExtras());
+        }
+        // We will set the package name for the originator of the remote request; this lets the
+        // receiving ConnectionService know that the request originated from a remote connection
+        // service so that it can provide tracking information for Telecom.
+        extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
+                mOurConnectionServiceImpl.getApplicationContext().getOpPackageName());
+
         final ConnectionRequest newRequest = new ConnectionRequest.Builder()
                 .setAccountHandle(request.getAccountHandle())
                 .setAddress(request.getAddress())
-                .setExtras(request.getExtras())
+                .setExtras(extras)
                 .setVideoState(request.getVideoState())
                 .setRttPipeFromInCall(request.getRttPipeFromInCall())
                 .setRttPipeToInCall(request.getRttPipeToInCall())
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6c8dc00..7d20d0d 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -758,6 +758,13 @@
     @SystemApi
     public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
 
+    /**
+     * Client disconnected for unspecified reason. This could for example be because the AP is being
+     * shut down.
+     * @hide
+     */
+    public static final int SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED = 2;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"IFACE_IP_MODE_"}, value = {