Merge "Add test for tapping Work share tab when work profile is off" into rvc-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index ed4c9b1..66b5015 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1298,6 +1298,130 @@
 
 }
 
+package android.hardware.hdmi {
+
+  public final class HdmiControlManager {
+    method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
+    method @RequiresPermission("android.permission.HDMI_CEC") public void setStandbyMode(boolean);
+    field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
+    field public static final int AVR_VOLUME_MUTED = 101; // 0x65
+    field public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 162; // 0xa2
+    field public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 160; // 0xa0
+    field public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 161; // 0xa1
+    field public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 128; // 0x80
+    field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 2; // 0x2
+    field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 1; // 0x1
+    field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0; // 0x0
+    field public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1; // 0x1
+    field public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3; // 0x3
+    field public static final int CONTROL_STATE_CHANGED_REASON_START = 0; // 0x0
+    field public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2; // 0x2
+    field public static final int DEVICE_EVENT_ADD_DEVICE = 1; // 0x1
+    field public static final int DEVICE_EVENT_REMOVE_DEVICE = 2; // 0x2
+    field public static final int DEVICE_EVENT_UPDATE_DEVICE = 3; // 0x3
+    field public static final String EXTRA_MESSAGE_EXTRA_PARAM1 = "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
+    field public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID";
+    field public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 18; // 0x12
+    field public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 51; // 0x33
+    field public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 49; // 0x31
+    field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 13; // 0xd
+    field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 14; // 0xe
+    field public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 50; // 0x32
+    field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 10; // 0xa
+    field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 9; // 0x9
+    field public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 21; // 0x15
+    field public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 19; // 0x13
+    field public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 22; // 0x16
+    field public static final int ONE_TOUCH_RECORD_NO_MEDIA = 16; // 0x10
+    field public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 12; // 0xc
+    field public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 20; // 0x14
+    field public static final int ONE_TOUCH_RECORD_OTHER_REASON = 31; // 0x1f
+    field public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 23; // 0x17
+    field public static final int ONE_TOUCH_RECORD_PLAYING = 17; // 0x11
+    field public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 48; // 0x30
+    field public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 27; // 0x1b
+    field public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 3; // 0x3
+    field public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 1; // 0x1
+    field public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 2; // 0x2
+    field public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 4; // 0x4
+    field public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 26; // 0x1a
+    field public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 6; // 0x6
+    field public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 5; // 0x5
+    field public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 7; // 0x7
+    field public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 11; // 0xb
+    field public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1; // 0x1
+    field public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2; // 0x2
+    field public static final int POWER_STATUS_ON = 0; // 0x0
+    field public static final int POWER_STATUS_STANDBY = 1; // 0x1
+    field public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; // 0x2
+    field public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; // 0x3
+    field public static final int POWER_STATUS_UNKNOWN = -1; // 0xffffffff
+    field @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; // 0x4
+    field public static final int RESULT_COMMUNICATION_FAILED = 7; // 0x7
+    field public static final int RESULT_EXCEPTION = 5; // 0x5
+    field public static final int RESULT_INCORRECT_MODE = 6; // 0x6
+    field public static final int RESULT_SOURCE_NOT_AVAILABLE = 2; // 0x2
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final int RESULT_TARGET_NOT_AVAILABLE = 3; // 0x3
+    field public static final int RESULT_TIMEOUT = 1; // 0x1
+    field public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 3; // 0x3
+    field public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 1; // 0x1
+    field public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 2; // 0x2
+    field public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0; // 0x0
+    field public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2; // 0x2
+    field public static final int TIMER_RECORDING_TYPE_DIGITAL = 1; // 0x1
+    field public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3; // 0x3
+    field public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 2; // 0x2
+    field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0; // 0x0
+    field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 1; // 0x1
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 6; // 0x6
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 10; // 0xa
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 2; // 0x2
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 14; // 0xe
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 5; // 0x5
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 4; // 0x4
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 3; // 0x3
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 7; // 0x7
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 1; // 0x1
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON = 9; // 0x9
+    field public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 8; // 0x8
+    field public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 8; // 0x8
+    field public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 11; // 0xb
+    field public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 9; // 0x9
+    field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa
+  }
+
+  public final class HdmiControlServiceWrapper {
+    ctor public HdmiControlServiceWrapper();
+    method @NonNull public android.hardware.hdmi.HdmiControlManager createHdmiControlManager();
+    method @BinderThread public void setDeviceTypes(@NonNull int[]);
+    method @BinderThread public void setPortInfo(@NonNull java.util.List<android.hardware.hdmi.HdmiPortInfo>);
+    field public static final int DEVICE_PURE_CEC_SWITCH = 6; // 0x6
+  }
+
+  public final class HdmiPortInfo implements android.os.Parcelable {
+    ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean);
+    method public int describeContents();
+    method public int getAddress();
+    method public int getId();
+    method public int getType();
+    method public boolean isArcSupported();
+    method public boolean isCecSupported();
+    method public boolean isMhlSupported();
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiPortInfo> CREATOR;
+    field public static final int PORT_INPUT = 0; // 0x0
+    field public static final int PORT_OUTPUT = 1; // 0x1
+  }
+
+  public class HdmiSwitchClient {
+    method public int getDeviceType();
+    method @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo();
+    method public void sendKeyEvent(int, boolean);
+    method public void sendVendorCommand(int, byte[], boolean);
+  }
+
+}
+
 package android.hardware.lights {
 
   public final class Light implements android.os.Parcelable {
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 44acbca..f82c8f1 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -61,8 +61,7 @@
                               const ResourceId& target_resource) {
   static constexpr const PolicyBitmask sDefaultPolicies =
       PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION |
-      PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE |
-      PolicyFlags::ACTOR_SIGNATURE;
+      PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE;
 
   // If the resource does not have an overlayable definition, allow the resource to be overlaid if
   // the overlay is preinstalled or signed with the same signature as the target.
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 5754eaf..de039f4 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -287,26 +287,66 @@
                               R::overlay::string::str4, false /* rewrite */));
 }
 
-
-// Overlays that are pre-installed or are signed with the same signature as the target/actor can
+// Overlays that are neither pre-installed nor signed with the same signature as the target cannot
 // overlay packages that have not defined overlayable resources.
-TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
-  constexpr PolicyBitmask kDefaultPolicies =
-      PolicyFlags::SIGNATURE | PolicyFlags::ACTOR_SIGNATURE | PolicyFlags::PRODUCT_PARTITION |
-      PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION | PolicyFlags::ODM_PARTITION |
-      PolicyFlags::OEM_PARTITION;
+TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
+  auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
+                                          "/overlay/overlay-no-name.apk", PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
 
-  for (PolicyBitmask policy = 1U << (sizeof(PolicyBitmask) * 8 - 1); policy > 0;
-       policy = policy >> 1U) {
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
+}
+
+// Overlays that are pre-installed or are signed with the same signature as the target can overlay
+// packages that have not defined overlayable resources.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
+  auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
     auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
                                             "/system-overlay-invalid/system-overlay-invalid.apk",
-                                            policy, /* enforce_overlayable */ true);
-    ASSERT_TRUE(resources) << resources.GetErrorMessage();
+                                            fulfilled_policies,
+                                            /* enforce_overlayable */ true);
 
-    const size_t expected_overlaid = (policy & kDefaultPolicies) != 0 ? 10U : 0U;
-    ASSERT_EQ(expected_overlaid, resources->GetTargetToOverlayMap().size())
-        << "Incorrect number of resources overlaid through policy " << policy;
-  }
+    ASSERT_TRUE(resources) << resources.GetErrorMessage();
+    auto& res = *resources;
+    ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 10U);
+    ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::not_overlayable,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::other, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::other, false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_actor,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_odm,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_oem,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_product,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_public,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_signature,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_system,
+                                false /* rewrite */));
+    ASSERT_RESULT(MappingExists(
+        res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE,
+        R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */));
+  };
+
+  CheckEntries(PolicyFlags::SIGNATURE);
+  CheckEntries(PolicyFlags::PRODUCT_PARTITION);
+  CheckEntries(PolicyFlags::SYSTEM_PARTITION);
+  CheckEntries(PolicyFlags::VENDOR_PARTITION);
+  CheckEntries(PolicyFlags::ODM_PARTITION);
+  CheckEntries(PolicyFlags::OEM_PARTITION);
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 36a8b2c..81d059e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -444,6 +444,11 @@
         TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"];
         MediaOutputOpSwitchReported mediaoutput_op_switch_reported =
             277 [(module) = "settings"];
+        CellBroadcastMessageFiltered cb_message_filtered =
+            278 [(module) = "cellbroadcast"];
+        TvTunerDvrStatus tv_tuner_dvr_status = 279 [(module) = "framework"];
+        TvCasSessionOpenStatus tv_cas_session_open_status =
+            280 [(module) = "framework"];
 
         // StatsdStats tracks platform atoms with ids upto 500.
         // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -9134,8 +9139,10 @@
 /**
  * Logs when a cell broadcast message is received on the device.
  *
- * Logged from CellBroadcastService module:
+ * Logged from Cell Broadcast module and platform:
  *   packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/
+ *   packages/apps/CellBroadcastReceiver/
+ *   frameworks/opt/telephony/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
  */
 message CellBroadcastMessageReported {
     // The type of Cell Broadcast message
@@ -9146,8 +9153,40 @@
         CDMA_SPC = 3;
     }
 
+    // The parts of the cell broadcast message pipeline
+    enum ReportSource {
+        UNKNOWN_SOURCE = 0;
+        FRAMEWORK = 1;
+        CB_SERVICE = 2;
+        CB_RECEIVER_APP = 3;
+    }
+
     // GSM, CDMA, CDMA-SCP
     optional CbType type = 1;
+
+    // The source of the report
+    optional ReportSource source = 2;
+}
+
+/**
+ * Logs when a cell broadcast message is filtered out, or otherwise intentionally not sent to CBR.
+ *
+ * Logged from CellBroadcastService module:
+ *   packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/
+ */
+message CellBroadcastMessageFiltered {
+    enum FilterReason {
+        NOT_FILTERED = 0;
+        DUPLICATE_MESSAGE = 1;
+        GEOFENCED_MESSAGE = 2;
+        AREA_INFO_MESSAGE = 3;
+    }
+
+    // GSM, CDMA, CDMA-SCP
+    optional CellBroadcastMessageReported.CbType type = 1;
+
+    // The source of the report
+    optional FilterReason filter = 2;
 }
 
 /**
@@ -9174,6 +9213,7 @@
         UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK = 12;
         UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK = 13;
         UNEXPECTED_CDMA_SCP_MESSAGE_TYPE_FROM_FWK = 14;
+        NO_CONNECTION_TO_CB_SERVICE = 15;
     }
 
     // What kind of error occurred
@@ -9205,6 +9245,58 @@
     //  new state
     optional State state = 2;
 }
+
+/**
+ * Logs the status of a dvr playback or record.
+ * This is atom ID 279.
+ *
+ * Logged from:
+ *   frameworks/base/media/java/android/media/tv/tuner/dvr
+ */
+message TvTunerDvrStatus {
+    enum Type {
+        UNKNOWN_TYPE = 0;
+        PLAYBACK = 1; // is a playback
+        RECORD = 2; // is a record
+    }
+    enum State {
+        UNKNOWN_STATE = 0;
+        STARTED = 1; // DVR is started
+        STOPPED = 2; // DVR is stopped
+    }
+    // The uid of the application that sent this custom atom.
+    optional int32 uid = 1 [(is_uid) = true];
+    // DVR type
+    optional Type type = 2;
+    //  DVR state
+    optional State state = 3;
+    //  Identify the segment of a record or playback
+    optional int32 segment_id = 4;
+    // indicate how many overflow or underflow happened between started to stopped
+    optional int32 overflow_underflow_count = 5;
+}
+
+/**
+ * Logs when a cas session opened through MediaCas.
+ * This is atom ID 280.
+ *
+ * Logged from:
+ *   frameworks/base/media/java/android/media/MediaCas.java
+ */
+message TvCasSessionOpenStatus {
+    enum State {
+        UNKNOWN = 0;
+        SUCCEEDED = 1; // indicate that the session is opened successfully.
+        FAILED = 2; // indicate that the session isn’t opened successfully.
+    }
+    // The uid of the application that sent this custom atom.
+    optional int32 uid = 1 [(is_uid) = true];
+    //  Cas system Id
+    optional int32 cas_system_id = 2;
+    // State of the session
+    optional State state = 3;
+}
+
 /**
  * Logs when an app is frozen or unfrozen.
  *
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 3de5b99..505b239 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -75,7 +75,7 @@
         if (!mSplitBucketForAppUpgrade) {
             return;
         }
-        if (mIsPulled && mCondition) {
+        if (mIsPulled && mCondition == ConditionState::kTrue) {
             pullAndMatchEventsLocked(eventTimeNs);
         }
         flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
@@ -84,7 +84,7 @@
     // ValueMetric needs special logic if it's a pulled atom.
     void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
         std::lock_guard<std::mutex> lock(mMutex);
-        if (mIsPulled && mCondition) {
+        if (mIsPulled && mCondition == ConditionState::kTrue) {
             pullAndMatchEventsLocked(eventTimeNs);
         }
         flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 1bcc35d..58a3259 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -4722,6 +4722,46 @@
     EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long());
 }
 
+/*
+ * Test bucket splits when condition is unknown.
+ */
+TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) {
+    ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    sp<ValueMetricProducer> valueProducer =
+            ValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric,
+                    ConditionState::kUnknown);
+
+    // App update event.
+    int64_t appUpdateTimeNs = bucketStartTimeNs + 1000;
+    valueProducer->notifyAppUpgrade(appUpdateTimeNs);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds
+    valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_value_metrics());
+    ASSERT_EQ(0, report.value_metrics().data_size());
+    ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+              report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+    EXPECT_EQ(NanoToMillis(appUpdateTimeNs),
+              report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+    ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+    auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+    EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+    EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cffa59c..108b9ee 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3252,18 +3252,56 @@
     @Override
     public void handleFixedRotationAdjustments(@NonNull IBinder token,
             @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
-        final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
-                ? displayAdjustments -> displayAdjustments.setFixedRotationAdjustments(
-                        fixedRotationAdjustments)
-                : null;
+        handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */);
+    }
+
+    /**
+     * Applies the rotation adjustments to override display information in resources belong to the
+     * provided token. If the token is activity token, the adjustments also apply to application
+     * because the appearance of activity is usually more sensitive to the application resources.
+     *
+     * @param token The token to apply the adjustments.
+     * @param fixedRotationAdjustments The information to override the display adjustments of
+     *                                 corresponding resources. If it is null, the exiting override
+     *                                 will be cleared.
+     * @param overrideConfig The override configuration of activity. It is used to override
+     *                       application configuration. If it is non-null, it means the token is
+     *                       confirmed as activity token. Especially when launching new activity,
+     *                       {@link #mActivities} hasn't put the new token.
+     */
+    private void handleFixedRotationAdjustments(@NonNull IBinder token,
+            @Nullable FixedRotationAdjustments fixedRotationAdjustments,
+            @Nullable Configuration overrideConfig) {
+        // The element of application configuration override is set only if the application
+        // adjustments are needed, because activity already has its own override configuration.
+        final Configuration[] appConfigOverride;
+        final Consumer<DisplayAdjustments> override;
+        if (fixedRotationAdjustments != null) {
+            appConfigOverride = new Configuration[1];
+            override = displayAdjustments -> {
+                displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
+                if (appConfigOverride[0] != null) {
+                    displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]);
+                }
+            };
+        } else {
+            appConfigOverride = null;
+            override = null;
+        }
         if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
             // No resources are associated with the token.
             return;
         }
-        if (mActivities.get(token) == null) {
-            // Only apply the override to application for activity token because the appearance of
-            // activity is usually more sensitive to the application resources.
-            return;
+        if (overrideConfig == null) {
+            final ActivityClientRecord r = mActivities.get(token);
+            if (r == null) {
+                // It is not an activity token. Nothing to do for application.
+                return;
+            }
+            overrideConfig = r.overrideConfig;
+        }
+        if (appConfigOverride != null) {
+            appConfigOverride[0] = overrideConfig;
         }
 
         // Apply the last override to application resources for compatibility. Because the Resources
@@ -3503,7 +3541,8 @@
         // The rotation adjustments must be applied before creating the activity, so the activity
         // can get the adjusted display info during creation.
         if (r.mPendingFixedRotationAdjustments != null) {
-            handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments);
+            handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments,
+                    r.overrideConfig);
             r.mPendingFixedRotationAdjustments = null;
         }
 
@@ -7388,6 +7427,10 @@
         }
     }
 
+    public Bundle getCoreSettings() {
+        return mCoreSettings;
+    }
+
     public int getIntCoreSetting(String key, int defaultValue) {
         synchronized (mResourcesManager) {
             if (mCoreSettings != null) {
@@ -7397,6 +7440,18 @@
         }
     }
 
+    /**
+     * Get the string value of the given key from core settings.
+     */
+    public String getStringCoreSetting(String key, String defaultValue) {
+        synchronized (mResourcesManager) {
+            if (mCoreSettings != null) {
+                return mCoreSettings.getString(key, defaultValue);
+            }
+            return defaultValue;
+        }
+    }
+
     float getFloatCoreSetting(String key, float defaultValue) {
         synchronized (mResourcesManager) {
             if (mCoreSettings != null) {
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 2d06ee8..b68639e 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -216,4 +216,14 @@
      *                             in {@link android.content.pm.ActivityInfo}.
      */
      void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation);
+
+    /**
+     * Called when a rotation is about to start on the foreground activity.
+     * This applies for:
+     *   * free sensor rotation
+     *   * forced rotation
+     *   * rotation settings set through adb command line
+     *   * rotation that occurs when rotation tile is toggled in quick settings
+     */
+     void onActivityRotation();
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 10f7835..f9b48e7 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -38,6 +38,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
+import android.os.GraphicsEnvironment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
@@ -46,6 +47,7 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.security.net.config.NetworkSecurityConfigProvider;
 import android.sysprop.VndkProperties;
 import android.text.TextUtils;
@@ -824,6 +826,32 @@
 
         final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
 
+        if (mActivityThread != null) {
+            final String gpuDebugApp = mActivityThread.getStringCoreSetting(
+                    Settings.Global.GPU_DEBUG_APP, "");
+            if (!gpuDebugApp.isEmpty() && mPackageName.equals(gpuDebugApp)) {
+
+                // The current application is used to debug, attempt to get the debug layers.
+                try {
+                    // Get the ApplicationInfo from PackageManager so that metadata fields present.
+                    final ApplicationInfo ai = ActivityThread.getPackageManager()
+                            .getApplicationInfo(mPackageName, PackageManager.GET_META_DATA,
+                                    UserHandle.myUserId());
+                    final String debugLayerPath = GraphicsEnvironment.getInstance()
+                            .getDebugLayerPathsFromSettings(mActivityThread.getCoreSettings(),
+                                    ActivityThread.getPackageManager(), mPackageName, ai);
+                    if (debugLayerPath != null) {
+                        libraryPermittedPath += File.pathSeparator + debugLayerPath;
+                    }
+                } catch (RemoteException e) {
+                    // Unlikely to fail for applications, but in case of failure, something is wrong
+                    // inside the system server, hence just skip.
+                    Slog.e(ActivityThread.TAG,
+                            "RemoteException when fetching debug layer paths for: " + mPackageName);
+                }
+            }
+        }
+
         // If we're not asked to include code, we construct a classloader that has
         // no code path included. We still need to set up the library search paths
         // and permitted path because NativeActivity relies on it (it attempts to
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 5d8daf8..843d1c7 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -199,4 +199,8 @@
     @Override
     public void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) {
     }
+
+    @Override
+    public void onActivityRotation() {
+    }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e331471..be3cfef 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1884,6 +1884,9 @@
 
     /**
      * Activity action: Launch UI to manage auto-revoke state.
+     *
+     * This is equivalent to Intent#ACTION_APPLICATION_DETAILS_SETTINGS
+     *
      * <p>
      * Input: {@link Intent#setData data} should be a {@code package}-scheme {@link Uri} with
      * a package name, whose auto-revoke state will be reviewed (mandatory).
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index ed75504..fc4ccd0 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1449,6 +1449,13 @@
         /** {@hide} */
         public static final int UID_UNKNOWN = -1;
 
+        /**
+         * This value is derived from the maximum file name length. No package above this limit
+         * can ever be successfully installed on the device.
+         * @hide
+         */
+        public static final int MAX_PACKAGE_NAME_LENGTH = 255;
+
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public int mode = MODE_INVALID;
@@ -1642,6 +1649,8 @@
 
         /**
          * Optionally set a label representing the app being installed.
+         *
+         * This value will be trimmed to the first 1000 characters.
          */
         public void setAppLabel(@Nullable CharSequence appLabel) {
             this.appLabel = (appLabel != null) ? appLabel.toString() : null;
@@ -1711,7 +1720,8 @@
          *
          * <p>Initially, all restricted permissions are whitelisted but you can change
          * which ones are whitelisted by calling this method or the corresponding ones
-         * on the {@link PackageManager}.
+         * on the {@link PackageManager}. Only soft or hard restricted permissions on the current
+         * Android version are supported and any invalid entries will be removed.
          *
          * @see PackageManager#addWhitelistedRestrictedPermission(String, String, int)
          * @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int)
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index f354bdb..65ce1e7 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -49,8 +49,16 @@
  * in the implementation of Parcelable in subclasses.
  */
 public class PackageItemInfo {
-    /** The maximum length of a safe label, in characters */
-    private static final int MAX_SAFE_LABEL_LENGTH = 50000;
+
+    /**
+     * The maximum length of a safe label, in characters
+     *
+     * TODO(b/157997155): It may make sense to expose this publicly so that apps can check for the
+     *  value and truncate the strings/use a different label, without having to hardcode and make
+     *  assumptions about the value.
+     * @hide
+     */
+    public static final int MAX_SAFE_LABEL_LENGTH = 1000;
 
     /** @hide */
     public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
index f64560a..fb8fd74 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -302,7 +302,14 @@
         }
 
         String permission = array.getNonConfigurationString(permissionAttr, 0);
-        activity.setPermission(permission != null ? permission : pkg.getPermission());
+        if (isAlias) {
+            // An alias will override permissions to allow referencing an Activity through its alias
+            // without needing the original permission. If an alias needs the same permission,
+            // it must be re-declared.
+            activity.setPermission(permission);
+        } else {
+            activity.setPermission(permission != null ? permission : pkg.getPermission());
+        }
 
         final boolean setExported = array.hasValue(exportedAttr);
         if (setExported) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java
index b37b617..6811e06 100644
--- a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java
@@ -20,7 +20,10 @@
 import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -29,9 +32,6 @@
 import android.util.TypedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.result.ParseInput;
-import android.content.pm.parsing.result.ParseResult;
 
 /** @hide */
 class ParsedComponentUtils {
@@ -60,16 +60,27 @@
         component.setName(className);
         component.setPackageName(packageName);
 
-        if (useRoundIcon) {
-            component.icon = array.getResourceId(roundIconAttr, 0);
+        int roundIconVal = useRoundIcon ? array.getResourceId(roundIconAttr, 0) : 0;
+        if (roundIconVal != 0) {
+            component.icon = roundIconVal;
+            component.nonLocalizedLabel = null;
+        } else {
+            int iconVal = array.getResourceId(iconAttr, 0);
+            if (iconVal != 0) {
+                component.icon = iconVal;
+                component.nonLocalizedLabel = null;
+            }
         }
 
-        if (component.icon == 0) {
-            component.icon = array.getResourceId(iconAttr, 0);
+        int logoVal = array.getResourceId(logoAttr, 0);
+        if (logoVal != 0) {
+            component.logo = logoVal;
         }
 
-        component.logo = array.getResourceId(logoAttr, 0);
-        component.banner = array.getResourceId(bannerAttr, 0);
+        int bannerVal = array.getResourceId(bannerAttr, 0);
+        if (bannerVal != 0) {
+            component.banner = bannerVal;
+        }
 
         if (descriptionAttr != null) {
             component.descriptionRes = array.getResourceId(descriptionAttr, 0);
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index bff8c39..a921215 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -1,6 +1,7 @@
 package android.hardware.hdmi;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.hardware.hdmi.HdmiControlManager.VendorCommandListener;
 import android.os.RemoteException;
@@ -84,7 +85,8 @@
      * @param hasVendorId {@code true} if the command type will be &lt;Vendor Command With ID&gt;.
      *                    {@code false} if the command will be &lt;Vendor Command&gt;
      */
-    public void sendVendorCommand(int targetAddress, byte[] params, boolean hasVendorId) {
+    public void sendVendorCommand(int targetAddress,
+            @SuppressLint("MissingNullability") byte[] params, boolean hasVendorId) {
         try {
             mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId);
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 6bc962b..1ce9b9c 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,8 +29,10 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
@@ -40,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * The {@link HdmiControlManager} class is used to send HDMI control messages
@@ -54,6 +58,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 @SystemService(Context.HDMI_CONTROL_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_HDMI_CEC)
 public final class HdmiControlManager {
@@ -136,6 +141,8 @@
     public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
     public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
 
+    /** @hide */
+    @SystemApi
     @IntDef ({
         RESULT_SUCCESS,
         RESULT_TIMEOUT,
@@ -397,8 +404,11 @@
      * See {@link HdmiDeviceInfo#DEVICE_PLAYBACK}
      * See {@link HdmiDeviceInfo#DEVICE_TV}
      * See {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM}
+     *
+     * @hide
      */
     @Nullable
+    @SystemApi
     @SuppressLint("Doclava125")
     public HdmiClient getClient(int type) {
         if (mService == null) {
@@ -427,8 +437,11 @@
      * system if the system is configured to host more than one type of HDMI-CEC logical devices.
      *
      * @return {@link HdmiPlaybackClient} instance. {@code null} on failure.
+     *
+     * @hide
      */
     @Nullable
+    @SystemApi
     @SuppressLint("Doclava125")
     public HdmiPlaybackClient getPlaybackClient() {
         return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK);
@@ -442,8 +455,11 @@
      * system if the system is configured to host more than one type of HDMI-CEC logical devices.
      *
      * @return {@link HdmiTvClient} instance. {@code null} on failure.
+     *
+     * @hide
      */
     @Nullable
+    @SystemApi
     @SuppressLint("Doclava125")
     public HdmiTvClient getTvClient() {
         return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV);
@@ -475,10 +491,8 @@
      * system if the system is configured to host more than one type of HDMI-CEC logical device.
      *
      * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
-     * @hide
      */
     @Nullable
-    @SystemApi
     @SuppressLint("Doclava125")
     public HdmiSwitchClient getSwitchClient() {
         return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
@@ -787,7 +801,10 @@
 
     /**
      * Listener used to get hotplug event from HDMI port.
+     *
+     * @hide
      */
+    @SystemApi
     public interface HotplugEventListener {
         void onReceived(HdmiHotplugEvent event);
     }
@@ -818,8 +835,29 @@
             mHdmiControlStatusChangeListeners = new ArrayMap<>();
 
     /**
-     * Listener used to get vendor-specific commands.
+     * Listener used to get the status of the HDMI CEC volume control feature (enabled/disabled).
+     * @hide
      */
+    public interface HdmiCecVolumeControlFeatureListener {
+        /**
+         * Called when the HDMI Control (CEC) volume control feature is enabled/disabled.
+         *
+         * @param enabled status of HDMI CEC volume control feature
+         * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()}
+         **/
+        void onHdmiCecVolumeControlFeature(boolean enabled);
+    }
+
+    private final ArrayMap<HdmiCecVolumeControlFeatureListener,
+            IHdmiCecVolumeControlFeatureListener>
+            mHdmiCecVolumeControlFeatureListeners = new ArrayMap<>();
+
+    /**
+     * Listener used to get vendor-specific commands.
+     *
+     * @hide
+     */
+    @SystemApi
     public interface VendorCommandListener {
         /**
          * Called when a vendor command is received.
@@ -858,7 +896,10 @@
      *
      * @param listener {@link HotplugEventListener} instance
      * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
+     *
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void addHotplugEventListener(HotplugEventListener listener) {
         if (mService == null) {
@@ -882,7 +923,10 @@
      * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}.
      *
      * @param listener {@link HotplugEventListener} instance to be removed
+     *
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void removeHotplugEventListener(HotplugEventListener listener) {
         if (mService == null) {
@@ -979,4 +1023,76 @@
         };
     }
 
+    /**
+     * Adds a listener to get informed of changes to the state of the HDMI CEC volume control
+     * feature.
+     *
+     * Upon adding a listener, the current state of the HDMI CEC volume control feature will be
+     * sent immediately.
+     *
+     * <p>To stop getting the notification,
+     * use {@link #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener)}.
+     *
+     * @param listener {@link HdmiCecVolumeControlFeatureListener} instance
+     * @hide
+     * @see #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener)
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void addHdmiCecVolumeControlFeatureListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull HdmiCecVolumeControlFeatureListener listener) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            return;
+        }
+        if (mHdmiCecVolumeControlFeatureListeners.containsKey(listener)) {
+            Log.e(TAG, "listener is already registered");
+            return;
+        }
+        IHdmiCecVolumeControlFeatureListener wrappedListener =
+                createHdmiCecVolumeControlFeatureListenerWrapper(executor, listener);
+        mHdmiCecVolumeControlFeatureListeners.put(listener, wrappedListener);
+        try {
+            mService.addHdmiCecVolumeControlFeatureListener(wrappedListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes a listener to stop getting informed of changes to the state of the HDMI CEC volume
+     * control feature.
+     *
+     * @param listener {@link HdmiCecVolumeControlFeatureListener} instance to be removed
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void removeHdmiCecVolumeControlFeatureListener(
+            HdmiCecVolumeControlFeatureListener listener) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            return;
+        }
+        IHdmiCecVolumeControlFeatureListener wrappedListener =
+                mHdmiCecVolumeControlFeatureListeners.remove(listener);
+        if (wrappedListener == null) {
+            Log.e(TAG, "tried to remove not-registered listener");
+            return;
+        }
+        try {
+            mService.removeHdmiCecVolumeControlFeatureListener(wrappedListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private IHdmiCecVolumeControlFeatureListener createHdmiCecVolumeControlFeatureListenerWrapper(
+            Executor executor, final HdmiCecVolumeControlFeatureListener listener) {
+        return new android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener.Stub() {
+            @Override
+            public void onHdmiCecVolumeControlFeature(boolean enabled) {
+                Binder.clearCallingIdentity();
+                executor.execute(() -> listener.onHdmiCecVolumeControlFeature(enabled));
+            }
+        };
+    }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
new file mode 100644
index 0000000..0289635
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -0,0 +1,469 @@
+/*
+ * 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 android.hardware.hdmi;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+
+import java.util.List;
+
+/**
+ * A wrapper of the Binder interface that clients running in the application process
+ * will use to perform HDMI-CEC features by communicating with other devices
+ * on the bus.
+ *
+ * @hide
+ */
+@TestApi
+public final class HdmiControlServiceWrapper {
+
+    /** Pure CEC switch device type. */
+    public static final int DEVICE_PURE_CEC_SWITCH = HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
+
+    private List<HdmiPortInfo> mInfoList = null;
+    private int[] mTypes = null;
+
+    /**
+     * Create a new HdmiControlManager with the current HdmiControlService wrapper
+     *
+     * @return the created HdmiControlManager
+     */
+    @NonNull
+    public HdmiControlManager createHdmiControlManager() {
+        return new HdmiControlManager(mInterface);
+    }
+
+    private final IHdmiControlService mInterface = new IHdmiControlService.Stub() {
+
+        @Override
+        public int[] getSupportedTypes() {
+            return HdmiControlServiceWrapper.this.getSupportedTypes();
+        }
+
+        @Override
+        public HdmiDeviceInfo getActiveSource() {
+            return HdmiControlServiceWrapper.this.getActiveSource();
+        }
+
+        @Override
+        public void oneTouchPlay(IHdmiControlCallback callback) {
+            HdmiControlServiceWrapper.this.oneTouchPlay(callback);
+        }
+
+        @Override
+        public void queryDisplayStatus(IHdmiControlCallback callback) {
+            HdmiControlServiceWrapper.this.queryDisplayStatus(callback);
+        }
+
+        @Override
+        public void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) {
+            HdmiControlServiceWrapper.this.addHdmiControlStatusChangeListener(listener);
+        }
+
+        @Override
+        public void removeHdmiControlStatusChangeListener(
+                IHdmiControlStatusChangeListener listener) {
+            HdmiControlServiceWrapper.this.removeHdmiControlStatusChangeListener(listener);
+        }
+
+        @Override
+        public void addHotplugEventListener(IHdmiHotplugEventListener listener) {
+            HdmiControlServiceWrapper.this.addHotplugEventListener(listener);
+        }
+
+        @Override
+        public void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
+            HdmiControlServiceWrapper.this.removeHotplugEventListener(listener);
+        }
+
+        @Override
+        public void addDeviceEventListener(IHdmiDeviceEventListener listener) {
+            HdmiControlServiceWrapper.this.addDeviceEventListener(listener);
+        }
+
+        @Override
+        public void deviceSelect(int deviceId, IHdmiControlCallback callback) {
+            HdmiControlServiceWrapper.this.deviceSelect(deviceId, callback);
+        }
+
+        @Override
+        public void portSelect(int portId, IHdmiControlCallback callback) {
+            HdmiControlServiceWrapper.this.portSelect(portId, callback);
+        }
+
+        @Override
+        public void sendKeyEvent(int deviceType, int keyCode, boolean isPressed) {
+            HdmiControlServiceWrapper.this.sendKeyEvent(deviceType, keyCode, isPressed);
+        }
+
+        @Override
+        public void sendVolumeKeyEvent(int deviceType, int keyCode, boolean isPressed) {
+            HdmiControlServiceWrapper.this.sendVolumeKeyEvent(deviceType, keyCode, isPressed);
+        }
+
+        @Override
+        public List<HdmiPortInfo> getPortInfo() {
+            return HdmiControlServiceWrapper.this.getPortInfo();
+        }
+
+        @Override
+        public boolean canChangeSystemAudioMode() {
+            return HdmiControlServiceWrapper.this.canChangeSystemAudioMode();
+        }
+
+        @Override
+        public boolean getSystemAudioMode() {
+            return HdmiControlServiceWrapper.this.getSystemAudioMode();
+        }
+
+        @Override
+        public int getPhysicalAddress() {
+            return HdmiControlServiceWrapper.this.getPhysicalAddress();
+        }
+
+        @Override
+        public void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
+            HdmiControlServiceWrapper.this.setSystemAudioMode(enabled, callback);
+        }
+
+        @Override
+        public void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
+            HdmiControlServiceWrapper.this.addSystemAudioModeChangeListener(listener);
+        }
+
+        @Override
+        public void removeSystemAudioModeChangeListener(
+                IHdmiSystemAudioModeChangeListener listener) {
+            HdmiControlServiceWrapper.this.removeSystemAudioModeChangeListener(listener);
+        }
+
+        @Override
+        public void setArcMode(boolean enabled) {
+            HdmiControlServiceWrapper.this.setArcMode(enabled);
+        }
+
+        @Override
+        public void setProhibitMode(boolean enabled) {
+            HdmiControlServiceWrapper.this.setProhibitMode(enabled);
+        }
+
+        @Override
+        public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) {
+            HdmiControlServiceWrapper.this.setSystemAudioVolume(oldIndex, newIndex, maxIndex);
+        }
+
+        @Override
+        public void setSystemAudioMute(boolean mute) {
+            HdmiControlServiceWrapper.this.setSystemAudioMute(mute);
+        }
+
+        @Override
+        public void setInputChangeListener(IHdmiInputChangeListener listener) {
+            HdmiControlServiceWrapper.this.setInputChangeListener(listener);
+        }
+
+        @Override
+        public List<HdmiDeviceInfo> getInputDevices() {
+            return HdmiControlServiceWrapper.this.getInputDevices();
+        }
+
+        @Override
+        public List<HdmiDeviceInfo> getDeviceList() {
+            return HdmiControlServiceWrapper.this.getDeviceList();
+        }
+
+        @Override
+        public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
+            HdmiControlServiceWrapper.this.powerOffRemoteDevice(logicalAddress, powerStatus);
+        }
+
+        @Override
+        public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
+            HdmiControlServiceWrapper.this.powerOnRemoteDevice(logicalAddress, powerStatus);
+        }
+
+        @Override
+        public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
+            HdmiControlServiceWrapper.this.askRemoteDeviceToBecomeActiveSource(physicalAddress);
+        }
+
+        @Override
+        public void sendVendorCommand(int deviceType, int targetAddress, byte[] params,
+                boolean hasVendorId) {
+            HdmiControlServiceWrapper.this.sendVendorCommand(
+                    deviceType, targetAddress, params, hasVendorId);
+        }
+
+        @Override
+        public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
+            HdmiControlServiceWrapper.this.addVendorCommandListener(listener, deviceType);
+        }
+
+        @Override
+        public void sendStandby(int deviceType, int deviceId) {
+            HdmiControlServiceWrapper.this.sendStandby(deviceType, deviceId);
+        }
+
+        @Override
+        public void setHdmiRecordListener(IHdmiRecordListener callback) {
+            HdmiControlServiceWrapper.this.setHdmiRecordListener(callback);
+        }
+
+        @Override
+        public void startOneTouchRecord(int recorderAddress, byte[] recordSource) {
+            HdmiControlServiceWrapper.this.startOneTouchRecord(recorderAddress, recordSource);
+        }
+
+        @Override
+        public void stopOneTouchRecord(int recorderAddress) {
+            HdmiControlServiceWrapper.this.stopOneTouchRecord(recorderAddress);
+        }
+
+        @Override
+        public void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
+            HdmiControlServiceWrapper.this.startTimerRecording(
+                    recorderAddress, sourceType, recordSource);
+        }
+
+        @Override
+        public void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
+            HdmiControlServiceWrapper.this.clearTimerRecording(
+                    recorderAddress, sourceType, recordSource);
+        }
+
+        @Override
+        public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) {
+            HdmiControlServiceWrapper.this.sendMhlVendorCommand(portId, offset, length, data);
+        }
+
+        @Override
+        public void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
+            HdmiControlServiceWrapper.this.addHdmiMhlVendorCommandListener(listener);
+        }
+
+        @Override
+        public void setStandbyMode(boolean isStandbyModeOn) {
+            HdmiControlServiceWrapper.this.setStandbyMode(isStandbyModeOn);
+        }
+
+        @Override
+        public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
+            HdmiControlServiceWrapper.this.setHdmiCecVolumeControlEnabled(
+                    isHdmiCecVolumeControlEnabled);
+        }
+
+        @Override
+        public boolean isHdmiCecVolumeControlEnabled() {
+            return HdmiControlServiceWrapper.this.isHdmiCecVolumeControlEnabled();
+        }
+
+        @Override
+        public void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute) {
+            HdmiControlServiceWrapper.this.reportAudioStatus(deviceType, volume, maxVolume, isMute);
+        }
+
+        @Override
+        public void setSystemAudioModeOnForAudioOnlySource() {
+            HdmiControlServiceWrapper.this.setSystemAudioModeOnForAudioOnlySource();
+        }
+
+        @Override
+        public void addHdmiCecVolumeControlFeatureListener(
+                IHdmiCecVolumeControlFeatureListener listener) {
+            HdmiControlServiceWrapper.this.addHdmiCecVolumeControlFeatureListener(listener);
+        }
+
+        @Override
+        public void removeHdmiCecVolumeControlFeatureListener(
+                IHdmiCecVolumeControlFeatureListener listener) {
+            HdmiControlServiceWrapper.this.removeHdmiCecVolumeControlFeatureListener(listener);
+        }
+    };
+
+    @BinderThread
+    public void setPortInfo(@NonNull List<HdmiPortInfo> infoList) {
+        mInfoList = infoList;
+    }
+
+    @BinderThread
+    public void setDeviceTypes(@NonNull int[] types) {
+        mTypes = types;
+    }
+
+    /** @hide */
+    public List<HdmiPortInfo> getPortInfo() {
+        return mInfoList;
+    }
+
+    /** @hide */
+    public int[] getSupportedTypes() {
+        return mTypes;
+    }
+
+    /** @hide */
+    public HdmiDeviceInfo getActiveSource() {
+        return null;
+    }
+
+    /** @hide */
+    public void oneTouchPlay(IHdmiControlCallback callback) {}
+
+    /** @hide */
+    public void queryDisplayStatus(IHdmiControlCallback callback) {}
+
+    /** @hide */
+    public void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) {}
+
+    /** @hide */
+    public void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) {}
+
+    /** @hide */
+    public void addHotplugEventListener(IHdmiHotplugEventListener listener) {}
+
+    /** @hide */
+    public void removeHotplugEventListener(IHdmiHotplugEventListener listener) {}
+
+    /** @hide */
+    public void addDeviceEventListener(IHdmiDeviceEventListener listener) {}
+
+    /** @hide */
+    public void deviceSelect(int deviceId, IHdmiControlCallback callback) {}
+
+    /** @hide */
+    public void portSelect(int portId, IHdmiControlCallback callback) {}
+
+    /** @hide */
+    public void sendKeyEvent(int deviceType, int keyCode, boolean isPressed) {}
+
+    /** @hide */
+    public void sendVolumeKeyEvent(int deviceType, int keyCode, boolean isPressed) {}
+
+    /** @hide */
+    public boolean canChangeSystemAudioMode() {
+        return true;
+    }
+
+    /** @hide */
+    public boolean getSystemAudioMode() {
+        return true;
+    }
+
+    /** @hide */
+    public int getPhysicalAddress() {
+        return 0xffff;
+    }
+
+    /** @hide */
+    public void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {}
+
+    /** @hide */
+    public void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {}
+
+    /** @hide */
+    public void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {}
+
+    /** @hide */
+    public void setArcMode(boolean enabled) {}
+
+    /** @hide */
+    public void setProhibitMode(boolean enabled) {}
+
+    /** @hide */
+    public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) {}
+
+    /** @hide */
+    public void setSystemAudioMute(boolean mute) {}
+
+    /** @hide */
+    public void setInputChangeListener(IHdmiInputChangeListener listener) {}
+
+    /** @hide */
+    public List<HdmiDeviceInfo> getInputDevices() {
+        return null;
+    }
+
+    /** @hide */
+    public List<HdmiDeviceInfo> getDeviceList() {
+        return null;
+    }
+
+    /** @hide */
+    public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {}
+
+    /** @hide */
+    public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {}
+
+    /** @hide */
+    public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {}
+
+    /** @hide */
+    public void sendVendorCommand(int deviceType, int targetAddress, byte[] params,
+            boolean hasVendorId) {}
+
+    /** @hide */
+    public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {}
+
+    /** @hide */
+    public void sendStandby(int deviceType, int deviceId) {}
+
+    /** @hide */
+    public void setHdmiRecordListener(IHdmiRecordListener callback) {}
+
+    /** @hide */
+    public void startOneTouchRecord(int recorderAddress, byte[] recordSource) {}
+
+    /** @hide */
+    public void stopOneTouchRecord(int recorderAddress) {}
+
+    /** @hide */
+    public void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {}
+
+    /** @hide */
+    public void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {}
+
+    /** @hide */
+    public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) {}
+
+    /** @hide */
+    public void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {}
+
+    /** @hide */
+    public void setStandbyMode(boolean isStandbyModeOn) {}
+
+    /** @hide */
+    public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {}
+
+    /** @hide */
+    public boolean isHdmiCecVolumeControlEnabled() {
+        return true;
+    }
+
+    /** @hide */
+    public void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute) {}
+
+    /** @hide */
+    public void setSystemAudioModeOnForAudioOnlySource() {}
+
+    /** @hide */
+    public void addHdmiCecVolumeControlFeatureListener(
+            IHdmiCecVolumeControlFeatureListener listener) {}
+
+    /** @hide */
+    public void removeHdmiCecVolumeControlFeatureListener(
+            IHdmiCecVolumeControlFeatureListener listener) {}
+}
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index 2623458..52c3628 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,6 +29,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class HdmiPortInfo implements Parcelable {
     /** HDMI port type: Input */
     public static final int PORT_INPUT = 0;
@@ -153,7 +155,9 @@
      * @param dest The Parcel in which the object should be written.
      * @param flags Additional flags about how the object should be written.
      *        May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}.
+     * @hide
      */
+    @SystemApi
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mId);
@@ -187,4 +191,9 @@
                 && mCecSupported == other.mCecSupported && mArcSupported == other.mArcSupported
                 && mMhlSupported == other.mMhlSupported;
     }
+
+    @Override
+    public int hashCode() {
+        return mId;
+    }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
index 7833653..913edfd0 100644
--- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -18,6 +18,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult;
 import android.os.Binder;
 import android.os.RemoteException;
@@ -38,6 +39,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class HdmiSwitchClient extends HdmiClient {
 
     private static final String TAG = "HdmiSwitchClient";
@@ -187,11 +189,8 @@
      * <p>This returns an empty list when the current device does not have HDMI input.
      *
      * @return a list of {@link HdmiPortInfo}
-     *
-     * @hide
      */
     @NonNull
-    @SystemApi
     public List<HdmiPortInfo> getPortInfo() {
         try {
             return mService.getPortInfo();
diff --git a/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl
new file mode 100644
index 0000000..873438b
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+/**
+ * Listener used to get the status of the HDMI CEC volume control feature (enabled/disabled).
+ * @hide
+ */
+oneway interface IHdmiCecVolumeControlFeatureListener {
+
+    /**
+     * Called when the HDMI Control (CEC) volume control feature is enabled/disabled.
+     *
+     * @param enabled status of HDMI CEC volume control feature
+     * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()}
+     **/
+    void onHdmiCecVolumeControlFeature(boolean enabled);
+}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 3582a92..4c724ef 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -18,6 +18,7 @@
 
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.IHdmiControlStatusChangeListener;
 import android.hardware.hdmi.IHdmiDeviceEventListener;
@@ -44,6 +45,8 @@
     void queryDisplayStatus(IHdmiControlCallback callback);
     void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
     void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
+    void addHdmiCecVolumeControlFeatureListener(IHdmiCecVolumeControlFeatureListener listener);
+    void removeHdmiCecVolumeControlFeatureListener(IHdmiCecVolumeControlFeatureListener listener);
     void addHotplugEventListener(IHdmiHotplugEventListener listener);
     void removeHotplugEventListener(IHdmiHotplugEventListener listener);
     void addDeviceEventListener(IHdmiDeviceEventListener listener);
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index b0fca00..d7ca63a 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -260,4 +260,10 @@
      */
     public void notifyUserActionIfNecessary() {
     }
+
+    /** @hide */
+    @Override
+    public final boolean isUiContext() {
+        return true;
+    }
 }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 034e6a7..df58a6c 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -93,8 +94,8 @@
     private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3;
 
     private ClassLoader mClassLoader;
-    private String mLayerPath;
-    private String mDebugLayerPath;
+    private String mLibrarySearchPaths;
+    private String mLibraryPermittedPaths;
 
     /**
      * Set up GraphicsEnvironment
@@ -185,118 +186,131 @@
     }
 
     /**
-     * Store the layer paths available to the loader.
+     * Store the class loader for namespace lookup later.
      */
     public void setLayerPaths(ClassLoader classLoader,
-                              String layerPath,
-                              String debugLayerPath) {
+                              String searchPaths,
+                              String permittedPaths) {
         // We have to store these in the class because they are set up before we
         // have access to the Context to properly set up GraphicsEnvironment
         mClassLoader = classLoader;
-        mLayerPath = layerPath;
-        mDebugLayerPath = debugLayerPath;
+        mLibrarySearchPaths = searchPaths;
+        mLibraryPermittedPaths = permittedPaths;
+    }
+
+    /**
+     * Returns the debug layer paths from settings.
+     * Returns null if:
+     *     1) The application process is not debuggable or layer injection metadata flag is not
+     *        true; Or
+     *     2) ENABLE_GPU_DEBUG_LAYERS is not true; Or
+     *     3) Package name is not equal to GPU_DEBUG_APP.
+     */
+    public String getDebugLayerPathsFromSettings(
+            Bundle coreSettings, IPackageManager pm, String packageName,
+            ApplicationInfo ai) {
+        if (!debugLayerEnabled(coreSettings, packageName, ai)) {
+            return null;
+        }
+        Log.i(TAG, "GPU debug layers enabled for " + packageName);
+        String debugLayerPaths = "";
+
+        // Grab all debug layer apps and add to paths.
+        final String gpuDebugLayerApps =
+                coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP, "");
+        if (!gpuDebugLayerApps.isEmpty()) {
+            Log.i(TAG, "GPU debug layer apps: " + gpuDebugLayerApps);
+            // If a colon is present, treat this as multiple apps, so Vulkan and GLES
+            // layer apps can be provided at the same time.
+            final String[] layerApps = gpuDebugLayerApps.split(":");
+            for (int i = 0; i < layerApps.length; i++) {
+                String paths = getDebugLayerAppPaths(pm, layerApps[i]);
+                if (!paths.isEmpty()) {
+                    // Append the path so files placed in the app's base directory will
+                    // override the external path
+                    debugLayerPaths += paths + File.pathSeparator;
+                }
+            }
+        }
+        return debugLayerPaths;
     }
 
     /**
      * Return the debug layer app's on-disk and in-APK lib directories
      */
-    private static String getDebugLayerAppPaths(PackageManager pm, String app) {
+    private static String getDebugLayerAppPaths(IPackageManager pm, String packageName) {
         final ApplicationInfo appInfo;
         try {
-            appInfo = pm.getApplicationInfo(app, PackageManager.MATCH_ALL);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Debug layer app '" + app + "' not installed");
-
-            return null;
+            appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL,
+                    UserHandle.myUserId());
+        } catch (RemoteException e) {
+            return "";
+        }
+        if (appInfo == null) {
+            Log.w(TAG, "Debug layer app '" + packageName + "' not installed");
         }
 
         final String abi = chooseAbi(appInfo);
-
         final StringBuilder sb = new StringBuilder();
         sb.append(appInfo.nativeLibraryDir)
-            .append(File.pathSeparator);
-        sb.append(appInfo.sourceDir)
+            .append(File.pathSeparator)
+            .append(appInfo.sourceDir)
             .append("!/lib/")
             .append(abi);
         final String paths = sb.toString();
-
         if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths);
 
         return paths;
     }
 
-    /**
-     * Set up layer search paths for all apps
-     * If debuggable, check for additional debug settings
-     */
-    private void setupGpuLayers(
-            Context context, Bundle coreSettings, PackageManager pm, String packageName,
-            ApplicationInfo ai) {
-        String layerPaths = "";
-
+    private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) {
         // Only enable additional debug functionality if the following conditions are met:
         // 1. App is debuggable or device is rooted or layer injection metadata flag is true
         // 2. ENABLE_GPU_DEBUG_LAYERS is true
         // 3. Package name is equal to GPU_DEBUG_APP
+        if (!isDebuggable() && !canInjectLayers(ai)) {
+            return false;
+        }
+        final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
+        if (enable == 0) {
+            return false;
+        }
+        final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, "");
+        if (packageName == null
+                || (gpuDebugApp.isEmpty() || packageName.isEmpty())
+                || !gpuDebugApp.equals(packageName)) {
+            return false;
+        }
+        return true;
+    }
 
-        if (isDebuggable() || canInjectLayers(ai)) {
+    /**
+     * Set up layer search paths for all apps
+     */
+    private void setupGpuLayers(
+            Context context, Bundle coreSettings, PackageManager pm, String packageName,
+            ApplicationInfo ai) {
+        final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai);
+        String layerPaths = "";
+        if (enabled) {
+            layerPaths = mLibraryPermittedPaths;
 
-            final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
+            final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS);
+            Log.i(TAG, "Vulkan debug layer list: " + layers);
+            if (layers != null && !layers.isEmpty()) {
+                setDebugLayers(layers);
+            }
 
-            if (enable != 0) {
-
-                final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP);
-
-                if ((gpuDebugApp != null && packageName != null)
-                        && (!gpuDebugApp.isEmpty() && !packageName.isEmpty())
-                        && gpuDebugApp.equals(packageName)) {
-                    Log.i(TAG, "GPU debug layers enabled for " + packageName);
-
-                    // Prepend the debug layer path as a searchable path.
-                    // This will ensure debug layers added will take precedence over
-                    // the layers specified by the app.
-                    layerPaths = mDebugLayerPath + ":";
-
-                    // If there is a debug layer app specified, add its path.
-                    final String gpuDebugLayerApp =
-                            coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP);
-
-                    if (gpuDebugLayerApp != null && !gpuDebugLayerApp.isEmpty()) {
-                        Log.i(TAG, "GPU debug layer app: " + gpuDebugLayerApp);
-                        // If a colon is present, treat this as multiple apps, so Vulkan and GLES
-                        // layer apps can be provided at the same time.
-                        String[] layerApps = gpuDebugLayerApp.split(":");
-                        for (int i = 0; i < layerApps.length; i++) {
-                            String paths = getDebugLayerAppPaths(pm, layerApps[i]);
-                            if (paths != null) {
-                                // Append the path so files placed in the app's base directory will
-                                // override the external path
-                                layerPaths += paths + ":";
-                            }
-                        }
-                    }
-
-                    final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS);
-
-                    Log.i(TAG, "Vulkan debug layer list: " + layers);
-                    if (layers != null && !layers.isEmpty()) {
-                        setDebugLayers(layers);
-                    }
-
-                    final String layersGLES =
-                            coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES);
-
-                    Log.i(TAG, "GLES debug layer list: " + layersGLES);
-                    if (layersGLES != null && !layersGLES.isEmpty()) {
-                        setDebugLayersGLES(layersGLES);
-                    }
-                }
+            final String layersGLES =
+                    coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES);
+            Log.i(TAG, "GLES debug layer list: " + layersGLES);
+            if (layersGLES != null && !layersGLES.isEmpty()) {
+                setDebugLayersGLES(layersGLES);
             }
         }
 
         // Include the app's lib directory in all cases
-        layerPaths += mLayerPath;
-
+        layerPaths += mLibrarySearchPaths;
         setLayerPaths(mClassLoader, layerPaths);
     }
 
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 02b822a..772845d 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -817,6 +817,9 @@
 
             /** @hide */
             public @NonNull Builder permitActivityLeaks() {
+                synchronized (StrictMode.class) {
+                    sExpectedActivityInstanceCount.clear();
+                }
                 return disable(DETECT_VM_ACTIVITY_LEAKS);
             }
 
@@ -2586,8 +2589,10 @@
                 return;
             }
 
+            // Use the instance count from InstanceTracker as initial value.
             Integer expected = sExpectedActivityInstanceCount.get(klass);
-            Integer newExpected = expected == null ? 1 : expected + 1;
+            Integer newExpected =
+                    expected == null ? InstanceTracker.getInstanceCount(klass) + 1 : expected + 1;
             sExpectedActivityInstanceCount.put(klass, newExpected);
         }
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7845200..a8391c2 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4090,13 +4090,6 @@
     public static int getMaxSupportedUsers() {
         // Don't allow multiple users on certain builds
         if (android.os.Build.ID.startsWith("JVP")) return 1;
-        if (ActivityManager.isLowRamDeviceStatic()) {
-            // Low-ram devices are Svelte. Most of the time they don't get multi-user.
-            if ((Resources.getSystem().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
-                    != Configuration.UI_MODE_TYPE_TELEVISION) {
-                return 1;
-            }
-        }
         return SystemProperties.getInt("fw.max_users",
                 Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
     }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 4ca48cb..e8806a0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1365,6 +1365,7 @@
                 String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid(
                         android.os.Process.myUid());
                 if (packageNames == null || packageNames.length <= 0) {
+                    Log.w(TAG, "Missing package names; no storage volumes available");
                     return new StorageVolume[0];
                 }
                 packageName = packageNames[0];
@@ -1372,6 +1373,7 @@
             final int uid = ActivityThread.getPackageManager().getPackageUid(packageName,
                     PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
             if (uid <= 0) {
+                Log.w(TAG, "Missing UID; no storage volumes available");
                 return new StorageVolume[0];
             }
             return storageManager.getVolumeList(uid, packageName, flags);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 52764d5..e10fcea 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14254,15 +14254,6 @@
         public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
 
         /**
-         * Persistent user id that is last logged in to.
-         *
-         * They map to user ids, for example, 10, 11, 12.
-         *
-         * @hide
-         */
-        public static final String LAST_ACTIVE_USER_ID = "last_active_persistent_user_id";
-
-        /**
          * Whether we've enabled native flags health check on this device. Takes effect on
          * reboot. The value "1" enables native flags health check; otherwise it's disabled.
          * @hide
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index b34268d..a2489b9 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4325,6 +4325,15 @@
         public static final String ETWS_WARNING_TYPE = "etws_warning_type";
 
         /**
+         * ETWS (Earthquake and Tsunami Warning System) primary message or not (ETWS alerts only).
+         * <p>See {@link android.telephony.SmsCbEtwsInfo}</p>
+         * <P>Type: BOOLEAN</P>
+         *
+         * @hide        // TODO: Unhide this for S.
+         */
+        public static final String ETWS_IS_PRIMARY = "etws_is_primary";
+
+        /**
          * CMAS (Commercial Mobile Alert System) message class (CMAS alerts only).
          * <p>See {@link android.telephony.SmsCbCmasInfo}</p>
          * <P>Type: INTEGER</P>
@@ -4464,37 +4473,6 @@
                 CMAS_URGENCY,
                 CMAS_CERTAINTY
         };
-
-        /**
-         * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects.
-         * @hide
-         */
-        public static final String[] QUERY_COLUMNS_FWK = {
-                _ID,
-                SLOT_INDEX,
-                SUBSCRIPTION_ID,
-                GEOGRAPHICAL_SCOPE,
-                PLMN,
-                LAC,
-                CID,
-                SERIAL_NUMBER,
-                SERVICE_CATEGORY,
-                LANGUAGE_CODE,
-                MESSAGE_BODY,
-                MESSAGE_FORMAT,
-                MESSAGE_PRIORITY,
-                ETWS_WARNING_TYPE,
-                CMAS_MESSAGE_CLASS,
-                CMAS_CATEGORY,
-                CMAS_RESPONSE_TYPE,
-                CMAS_SEVERITY,
-                CMAS_URGENCY,
-                CMAS_CERTAINTY,
-                RECEIVED_TIME,
-                MESSAGE_BROADCASTED,
-                GEOMETRIES,
-                MAXIMUM_WAIT_TIME
-        };
     }
 
     /**
diff --git a/core/java/android/service/autofill/InlineSuggestionRoot.java b/core/java/android/service/autofill/InlineSuggestionRoot.java
index c879653..16c3f1d 100644
--- a/core/java/android/service/autofill/InlineSuggestionRoot.java
+++ b/core/java/android/service/autofill/InlineSuggestionRoot.java
@@ -58,7 +58,9 @@
             case MotionEvent.ACTION_DOWN: {
                 mDownX = event.getX();
                 mDownY = event.getY();
-            } break;
+            }
+            // Intentionally fall through to the next case so that when the window is obscured
+            // we transfer the touch to the remote IME window and don't handle it locally.
 
             case MotionEvent.ACTION_MOVE: {
                 final float distance = MathUtils.dist(mDownX, mDownY,
diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java
index d01bc25..8383072a 100644
--- a/core/java/android/service/controls/Control.java
+++ b/core/java/android/service/controls/Control.java
@@ -73,25 +73,37 @@
     })
     public @interface Status {};
 
+    /**
+     * Reserved for use with the {@link StatelessBuilder}, and while loading. When state is
+     * requested via {@link ControlsProviderService#createPublisherFor}, use other status codes
+     * to indicate the proper device state.
+     */
     public static final int STATUS_UNKNOWN = 0;
 
     /**
-     * The device corresponding to the {@link Control} is responding correctly.
+     * Used to indicate that the state of the device was successfully retrieved. This includes
+     * all scenarios where the device may have a warning for the user, such as "Lock jammed",
+     * or "Vacuum stuck". Any information for the user should be set through
+     * {@link StatefulBuilder#setStatusText}.
      */
     public static final int STATUS_OK = 1;
 
     /**
-     * The device corresponding to the {@link Control} cannot be found or was removed.
+     * The device corresponding to the {@link Control} cannot be found or was removed. The user
+     * will be alerted and directed to the application to resolve.
      */
     public static final int STATUS_NOT_FOUND = 2;
 
     /**
-     * The device corresponding to the {@link Control} is in an error state.
+     * Used to indicate that there was a temporary error while loading the device state. A default
+     * error message will be displayed in place of any custom text that was set through
+     * {@link StatefulBuilder#setStatusText}.
      */
     public static final int STATUS_ERROR = 3;
 
     /**
-     * The {@link Control} is currently disabled.
+     * The {@link Control} is currently disabled.  A default error message will be displayed in
+     * place of any custom text that was set through {@link StatefulBuilder#setStatusText}.
      */
     public static final int STATUS_DISABLED = 4;
 
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e4fbf9f..9b293eb 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -340,6 +340,10 @@
     /**
      *  Listen for display info changed event.
      *
+     *  Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
+     *  READ_PHONE_STATE} or that the calling app has carrier privileges (see
+     *  {@link TelephonyManager#hasCarrierPrivileges}).
+     *
      *  @see #onDisplayInfoChanged
      */
     public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000;
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index a9af595..55c527b 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.os.StrictMode.vmIncorrectContextUseEnabled;
+
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
@@ -26,9 +28,12 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.StrictMode;
 import android.os.SystemClock;
+import android.util.Log;
 
 import com.android.internal.util.FrameworkStatsLog;
 
@@ -228,6 +233,7 @@
         }
     }
 
+    private static final String TAG = GestureDetector.class.getSimpleName();
     @UnsupportedAppUsage
     private int mTouchSlopSquare;
     private int mDoubleTapTouchSlopSquare;
@@ -289,11 +295,6 @@
     private VelocityTracker mVelocityTracker;
 
     /**
-     * True if the detector can throw exception when touch steam is unexpected .
-     */
-    private boolean mExceptionForTouchStream;
-
-    /**
      * Consistency verifier for debugging purposes.
      */
     private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
@@ -383,7 +384,8 @@
      * You may only use this constructor from a {@link android.os.Looper} thread.
      * @see android.os.Handler#Handler()
      *
-     * @param context the application's context
+     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * {@link Context#createWindowContext(int, Bundle)}
      * @param listener the listener invoked for all the callbacks, this must
      * not be null. If the listener implements the {@link OnDoubleTapListener} or
      * {@link OnContextClickListener} then it will also be set as the listener for
@@ -400,7 +402,8 @@
      * thread associated with the supplied {@link android.os.Handler}.
      * @see android.os.Handler#Handler()
      *
-     * @param context the application's context
+     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * {@link Context#createWindowContext(int, Bundle)}
      * @param listener the listener invoked for all the callbacks, this must
      * not be null. If the listener implements the {@link OnDoubleTapListener} or
      * {@link OnContextClickListener} then it will also be set as the listener for
@@ -430,7 +433,8 @@
      * thread associated with the supplied {@link android.os.Handler}.
      * @see android.os.Handler#Handler()
      *
-     * @param context the application's context
+     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * {@link Context#createWindowContext(int, Bundle)}
      * @param listener the listener invoked for all the callbacks, this must
      * not be null.
      * @param handler the handler to use for running deferred listener events.
@@ -461,6 +465,17 @@
             mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
             mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier();
         } else {
+            if (!context.isUiContext() && vmIncorrectContextUseEnabled()) {
+                final String errorMessage =
+                        "Tried to access UI constants from a non-visual Context.";
+                final String message = "GestureDetector must be accessed from Activity or other "
+                        + "visual Context. Use an Activity or a Context created with "
+                        + "Context#createWindowContext(int, Bundle), which are adjusted to the "
+                        + "configuration and visual bounds of an area on screen.";
+                final Exception exception = new IllegalArgumentException(errorMessage);
+                StrictMode.onIncorrectContextUsed(message, exception);
+                Log.e(TAG, errorMessage + message, exception);
+            }
             final ViewConfiguration configuration = ViewConfiguration.get(context);
             touchSlop = configuration.getScaledTouchSlop();
             doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
@@ -472,8 +487,6 @@
         mTouchSlopSquare = touchSlop * touchSlop;
         mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
         mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
-        mExceptionForTouchStream = context != null
-                && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R;
     }
 
     /**
@@ -646,13 +659,6 @@
                 break;
 
             case MotionEvent.ACTION_MOVE:
-                if (mExceptionForTouchStream && !mStillDown) {
-                    throw new IllegalStateException("Incomplete event stream received: "
-                            + "Received ACTION_MOVE before ACTION_DOWN. ACTION_DOWN must precede "
-                            + "ACTION_MOVE following ACTION_UP or ACTION_CANCEL, or when this "
-                            + "GestureDetector has not yet received any events.");
-                }
-
                 if (mInLongPress || mInContextClick) {
                     break;
                 }
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 7042f29..4a65511 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -205,6 +205,8 @@
      * <p>Note that if the {@link WebChromeClient} is set to be {@code null},
      * or if {@link WebChromeClient} is not set at all, the default dialog will
      * be suppressed and Javascript execution will continue immediately.
+     * <p>Note that the default dialog does not inherit the {@link
+     * android.view.Display#FLAG_SECURE} flag from the parent window.
      *
      * @param view The WebView that initiated the callback.
      * @param url The url of the page requesting the dialog.
@@ -240,6 +242,8 @@
      * or if {@link WebChromeClient} is not set at all, the default dialog will
      * be suppressed and the default value of {@code false} will be returned to
      * the JavaScript code immediately.
+     * <p>Note that the default dialog does not inherit the {@link
+     * android.view.Display#FLAG_SECURE} flag from the parent window.
      *
      * @param view The WebView that initiated the callback.
      * @param url The url of the page requesting the dialog.
@@ -274,6 +278,8 @@
      * or if {@link WebChromeClient} is not set at all, the default dialog will
      * be suppressed and {@code null} will be returned to the JavaScript code
      * immediately.
+     * <p>Note that the default dialog does not inherit the {@link
+     * android.view.Display#FLAG_SECURE} flag from the parent window.
      *
      * @param view The WebView that initiated the callback.
      * @param url The url of the page requesting the dialog.
@@ -308,6 +314,8 @@
      * <p>Note that if the {@link WebChromeClient} is set to be {@code null},
      * or if {@link WebChromeClient} is not set at all, the default dialog will
      * be suppressed and the navigation will be resumed immediately.
+     * <p>Note that the default dialog does not inherit the {@link
+     * android.view.Display#FLAG_SECURE} flag from the parent window.
      *
      * @param view The WebView that initiated the callback.
      * @param url The url of the page requesting the dialog.
diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java
index d2614da..9ccb4c1 100644
--- a/core/java/android/window/VirtualDisplayTaskEmbedder.java
+++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java
@@ -365,8 +365,8 @@
             // Found the topmost stack on target display. Now check if the topmost task's
             // description changed.
             if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
-                mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this,
-                        taskInfo.taskDescription.getBackgroundColor());
+                mHost.post(()-> mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this,
+                        taskInfo.taskDescription.getBackgroundColor()));
             }
         }
 
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 493865a..b723db2 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -151,6 +151,13 @@
                     mOnProfileSelectedListener.onProfileSelected(position);
                 }
             }
+
+            @Override
+            public void onPageScrollStateChanged(int state) {
+                if (mOnProfileSelectedListener != null) {
+                    mOnProfileSelectedListener.onProfilePageStateChanged(state);
+                }
+            }
         });
         viewPager.setAdapter(this);
         viewPager.setCurrentItem(mCurrentPage);
@@ -606,6 +613,17 @@
          * {@link #PROFILE_WORK} if the work profile was selected.
          */
         void onProfileSelected(int profileIndex);
+
+
+        /**
+         * Callback for when the scroll state changes. Useful for discovering when the user begins
+         * dragging, when the pager is automatically settling to the current page, or when it is
+         * fully stopped/idle.
+         * @param state {@link ViewPager#SCROLL_STATE_IDLE}, {@link ViewPager#SCROLL_STATE_DRAGGING}
+         *              or {@link ViewPager#SCROLL_STATE_SETTLING}
+         * @see ViewPager.OnPageChangeListener#onPageScrollStateChanged
+         */
+        void onProfilePageStateChanged(int state);
     }
 
     /**
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 505b6b6..d5e9e50 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -130,6 +130,7 @@
 import com.android.internal.widget.GridLayoutManager;
 import com.android.internal.widget.RecyclerView;
 import com.android.internal.widget.ResolverDrawerLayout;
+import com.android.internal.widget.ViewPager;
 
 import com.google.android.collect.Lists;
 
@@ -205,6 +206,10 @@
     public static final int SELECTION_TYPE_STANDARD = 3;
     public static final int SELECTION_TYPE_COPY = 4;
 
+    private static final int SCROLL_STATUS_IDLE = 0;
+    private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
+    private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
+
     // statsd logger wrapper
     protected ChooserActivityLogger mChooserActivityLogger;
 
@@ -294,6 +299,7 @@
     protected MetricsLogger mMetricsLogger;
 
     private ContentPreviewCoordinator mPreviewCoord;
+    private int mScrollStatus = SCROLL_STATUS_IDLE;
 
     @VisibleForTesting
     protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
@@ -2841,10 +2847,20 @@
         final float defaultElevation = elevatedView.getElevation();
         final float chooserHeaderScrollElevation =
                 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
-
         mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
                 new RecyclerView.OnScrollListener() {
                     public void onScrollStateChanged(RecyclerView view, int scrollState) {
+                        if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
+                            if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
+                                mScrollStatus = SCROLL_STATUS_IDLE;
+                                setHorizontalScrollingEnabled(true);
+                            }
+                        } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
+                            if (mScrollStatus == SCROLL_STATUS_IDLE) {
+                                mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL;
+                                setHorizontalScrollingEnabled(false);
+                            }
+                        }
                     }
 
                     public void onScrolled(RecyclerView view, int dx, int dy) {
@@ -3056,6 +3072,33 @@
         return super.onApplyWindowInsets(v, insets);
     }
 
+    private void setHorizontalScrollingEnabled(boolean enabled) {
+        ResolverViewPager viewPager = findViewById(R.id.profile_pager);
+        viewPager.setSwipingEnabled(enabled);
+    }
+
+    private void setVerticalScrollEnabled(boolean enabled) {
+        ChooserGridLayoutManager layoutManager =
+                (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView()
+                        .getLayoutManager();
+        layoutManager.setVerticalScrollEnabled(enabled);
+    }
+
+    @Override
+    void onHorizontalSwipeStateChanged(int state) {
+        if (state == ViewPager.SCROLL_STATE_DRAGGING) {
+            if (mScrollStatus == SCROLL_STATUS_IDLE) {
+                mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL;
+                setVerticalScrollEnabled(false);
+            }
+        } else if (state == ViewPager.SCROLL_STATE_IDLE) {
+            if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
+                mScrollStatus = SCROLL_STATUS_IDLE;
+                setVerticalScrollEnabled(true);
+            }
+        }
+    }
+
     /**
      * Adapter for all types of items and targets in ShareSheet.
      * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
diff --git a/core/java/com/android/internal/app/ChooserGridLayoutManager.java b/core/java/com/android/internal/app/ChooserGridLayoutManager.java
index 317a987..c50ebd9 100644
--- a/core/java/com/android/internal/app/ChooserGridLayoutManager.java
+++ b/core/java/com/android/internal/app/ChooserGridLayoutManager.java
@@ -28,6 +28,8 @@
  */
 public class ChooserGridLayoutManager extends GridLayoutManager {
 
+    private boolean mVerticalScrollEnabled = true;
+
     /**
      * Constructor used when layout manager is set in XML by RecyclerView attribute
      * "layoutManager". If spanCount is not specified in the XML, it defaults to a
@@ -67,4 +69,13 @@
         // Do not count the footer view in the official count
         return super.getRowCountForAccessibility(recycler, state) - 1;
     }
+
+    void setVerticalScrollEnabled(boolean verticalScrollEnabled) {
+        mVerticalScrollEnabled = verticalScrollEnabled;
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        return mVerticalScrollEnabled && super.canScrollVertically();
+    }
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 71ee8af..15ba8e8 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -265,4 +265,16 @@
     void performDirectAction(in IBinder token, String actionId, in Bundle arguments, int taskId,
             IBinder assistToken, in RemoteCallback cancellationCallback,
             in RemoteCallback resultCallback);
+
+    /**
+     * Temporarily disables voice interaction (for example, on Automotive when the display is off).
+     *
+     * It will shutdown the service, and only re-enable it after it's called again (or after a
+     * system restart).
+     *
+     * NOTE: it's only effective when the service itself is available / enabled in the device, so
+     * calling setDisable(false) would be a no-op when it isn't.
+     */
+    void setDisabled(boolean disabled);
+
 }
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index e65d1fe..61a52bc 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 
+import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER;
 import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE;
 
 import android.annotation.Nullable;
@@ -246,6 +247,7 @@
         int selectedProfile = findSelectedProfile(className);
         sanitizeIntent(intentReceived);
         intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
+        intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId));
         startActivityAsCaller(intentReceived, null, null, false, userId);
         finish();
     }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 26fb08a..03b99ff 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -184,6 +184,18 @@
     static final String EXTRA_SELECTED_PROFILE =
             "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
 
+    /**
+     * {@link UserHandle} extra to indicate the user of the user that the starting intent
+     * originated from.
+     * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()},
+     * as there are edge cases when the intent resolver is launched in the other profile.
+     * For example, when we have 0 resolved apps in current profile and multiple resolved
+     * apps in the other profile, opening a link from the current profile launches the intent
+     * resolver in the other one. b/148536209 for more info.
+     */
+    static final String EXTRA_CALLING_USER =
+            "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER";
+
     static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
     static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
 
@@ -470,17 +482,20 @@
         // the intent resolver is started in the other profile. Since this is the only case when
         // this happens, we check for it here and set the current profile's tab.
         int selectedProfile = getCurrentProfile();
-        UserHandle intentUser = UserHandle.of(getLaunchingUserId());
+        UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER)
+                ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
+                : getUser();
         if (!getUser().equals(intentUser)) {
             if (getPersonalProfileUserHandle().equals(intentUser)) {
                 selectedProfile = PROFILE_PERSONAL;
             } else if (getWorkProfileUserHandle().equals(intentUser)) {
                 selectedProfile = PROFILE_WORK;
             }
-        }
-        int selectedProfileExtra = getSelectedProfileExtra();
-        if (selectedProfileExtra != -1) {
-            selectedProfile = selectedProfileExtra;
+        } else {
+            int selectedProfileExtra = getSelectedProfileExtra();
+            if (selectedProfileExtra != -1) {
+                selectedProfile = selectedProfileExtra;
+            }
         }
         // We only show the default app for the profile of the current user. The filterLastUsed
         // flag determines whether to show a default app and that app is not shown in the
@@ -535,22 +550,6 @@
         return selectedProfile;
     }
 
-    /**
-     * Returns the user id of the user that the starting intent originated from.
-     * <p>This is not necessarily equal to {@link #getUserId()} or {@link UserHandle#myUserId()},
-     * as there are edge cases when the intent resolver is launched in the other profile.
-     * For example, when we have 0 resolved apps in current profile and multiple resolved apps
-     * in the other profile, opening a link from the current profile launches the intent resolver
-     * in the other one. b/148536209 for more info.
-     */
-    private int getLaunchingUserId() {
-        int contentUserHint = getIntent().getContentUserHint();
-        if (contentUserHint == UserHandle.USER_CURRENT) {
-            return UserHandle.myUserId();
-        }
-        return contentUserHint;
-    }
-
     protected @Profile int getCurrentProfile() {
         return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK);
     }
@@ -1655,10 +1654,18 @@
         viewPager.setVisibility(View.VISIBLE);
         tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
         mMultiProfilePagerAdapter.setOnProfileSelectedListener(
-                index -> {
-                    tabHost.setCurrentTab(index);
-                    resetButtonBar();
-                    resetCheckedItem();
+                new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() {
+                    @Override
+                    public void onProfileSelected(int index) {
+                        tabHost.setCurrentTab(index);
+                        resetButtonBar();
+                        resetCheckedItem();
+                    }
+
+                    @Override
+                    public void onProfilePageStateChanged(int state) {
+                        onHorizontalSwipeStateChanged(state);
+                    }
                 });
         mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener(
                 () -> {
@@ -1670,6 +1677,8 @@
         findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE);
     }
 
+    void onHorizontalSwipeStateChanged(int state) {}
+
     private void maybeHideDivider() {
         if (!isIntentPicker()) {
             return;
@@ -1810,6 +1819,12 @@
         ResolverListAdapter activeListAdapter =
                 mMultiProfilePagerAdapter.getActiveListAdapter();
         View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider);
+        if (!useLayoutWithDefault()) {
+            int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+            buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+                    buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+                            R.dimen.resolver_button_bar_spacing) + inset);
+        }
         if (activeListAdapter.isTabLoaded()
                 && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)
                 && !useLayoutWithDefault()) {
@@ -1826,12 +1841,6 @@
         buttonLayout.setVisibility(View.VISIBLE);
         setButtonBarIgnoreOffset(/* ignoreOffset */ true);
 
-        if (!useLayoutWithDefault()) {
-            int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
-            buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
-                    buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
-                            R.dimen.resolver_button_bar_spacing) + inset);
-        }
         mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
         mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
 
diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java
index 4eb6e3b..9cdfc2f 100644
--- a/core/java/com/android/internal/app/ResolverViewPager.java
+++ b/core/java/com/android/internal/app/ResolverViewPager.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.internal.widget.ViewPager;
@@ -30,6 +31,8 @@
  */
 public class ResolverViewPager extends ViewPager {
 
+    private boolean mSwipingEnabled = true;
+
     public ResolverViewPager(Context context) {
         super(context);
     }
@@ -70,4 +73,13 @@
         heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
+
+    void setSwipingEnabled(boolean swipingEnabled) {
+        mSwipingEnabled = swipingEnabled;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return mSwipingEnabled && super.onInterceptTouchEvent(ev);
+    }
 }
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index 67ffd4d..5212265 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -60,4 +60,28 @@
      */
     void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName,
             @Nullable InstanceId instance);
+
+    /**
+     * Log an event with ranked-choice information along with package.
+     * Does nothing if event.getId() <= 0.
+     * @param event an enum implementing UiEventEnum interface.
+     * @param uid the uid of the relevant app, if known (0 otherwise).
+     * @param packageName the package name of the relevant app, if known (null otherwise).
+     * @param position the position picked.
+     */
+    void logWithPosition(@NonNull UiEventEnum event, int uid, @Nullable String packageName,
+            int position);
+
+    /**
+     * Log an event with ranked-choice information along with package and instance ID.
+     * Does nothing if event.getId() <= 0.
+     * @param event an enum implementing UiEventEnum interface.
+     * @param uid the uid of the relevant app, if known (0 otherwise).
+     * @param packageName the package name of the relevant app, if known (null otherwise).
+     * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to
+     *                 logWithPosition().
+     * @param position the position picked.
+     */
+    void logWithInstanceIdAndPosition(@NonNull UiEventEnum event, int uid,
+            @Nullable String packageName, @Nullable InstanceId instance, int position);
 }
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index 4d171ec..c9156c1 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -48,4 +48,31 @@
             log(event, uid, packageName);
         }
     }
+
+    @Override
+    public void logWithPosition(UiEventEnum event, int uid, String packageName, int position) {
+        final int eventID = event.getId();
+        if (eventID > 0) {
+            FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED,
+                    /* event_id = 1 */ eventID,
+                    /* package_name = 2 */ packageName,
+                    /* instance_id = 3 */ 0,
+                    /* position_picked = 4 */ position);
+        }
+    }
+
+    @Override
+    public void logWithInstanceIdAndPosition(UiEventEnum event, int uid, String packageName,
+            InstanceId instance, int position) {
+        final int eventID = event.getId();
+        if ((eventID > 0)  && (instance != null)) {
+            FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED,
+                    /* event_id = 1 */ eventID,
+                    /* package_name = 2 */ packageName,
+                    /* instance_id = 3 */ instance.getId(),
+                    /* position_picked = 4 */ position);
+        } else {
+            logWithPosition(event, uid, packageName, position);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 180ab08..2d09434 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -35,13 +35,15 @@
         public final int eventId;
         public final int uid;
         public final String packageName;
-        public final InstanceId instanceId;  // Used only for WithInstanceId variant
+        public final InstanceId instanceId;  // Used only for WithInstanceId variants
+        public final int position;  // Used only for Position variants
 
         FakeUiEvent(int eventId, int uid, String packageName) {
             this.eventId = eventId;
             this.uid = uid;
             this.packageName = packageName;
             this.instanceId = null;
+            this.position = 0;
         }
 
         FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId) {
@@ -49,6 +51,15 @@
             this.uid = uid;
             this.packageName = packageName;
             this.instanceId = instanceId;
+            this.position = 0;
+        }
+
+        FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId, int position) {
+            this.eventId = eventId;
+            this.uid = uid;
+            this.packageName = packageName;
+            this.instanceId = instanceId;
+            this.position = position;
         }
     }
 
@@ -92,4 +103,21 @@
             mLogs.add(new FakeUiEvent(eventId, uid, packageName, instance));
         }
     }
+
+    @Override
+    public void logWithPosition(UiEventEnum event, int uid, String packageName, int position) {
+        final int eventId = event.getId();
+        if (eventId > 0) {
+            mLogs.add(new FakeUiEvent(eventId, uid, packageName, null, position));
+        }
+    }
+
+    @Override
+    public void logWithInstanceIdAndPosition(UiEventEnum event, int uid, String packageName,
+            InstanceId instance, int position) {
+        final int eventId = event.getId();
+        if (eventId > 0) {
+            mLogs.add(new FakeUiEvent(eventId, uid, packageName, instance, position));
+        }
+    }
 }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index ad6c7e8..adc7ba3 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -8,10 +8,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.graphics.Bitmap;
 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;
@@ -37,10 +37,12 @@
         private int mSource;
         private boolean mHasStatusBar;
         private boolean mHasNavBar;
-        private Bitmap mBitmap;
+        private Bundle mBitmapBundle;
         private Rect mBoundsInScreen;
         private Insets mInsets;
         private int mTaskId;
+        private int mUserId;
+        private ComponentName mTopComponent;
 
         ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
             mSource = source;
@@ -48,24 +50,29 @@
             mHasNavBar = hasNav;
         }
 
-        ScreenshotRequest(
-                int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) {
+        ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets,
+                int taskId, int userId, ComponentName topComponent) {
             mSource = source;
-            mBitmap = bitmap;
+            mBitmapBundle = bitmapBundle;
             mBoundsInScreen = boundsInScreen;
             mInsets = insets;
             mTaskId = taskId;
+            mUserId = userId;
+            mTopComponent = topComponent;
         }
 
         ScreenshotRequest(Parcel in) {
             mSource = in.readInt();
             mHasStatusBar = in.readBoolean();
             mHasNavBar = in.readBoolean();
+
             if (in.readInt() == 1) {
-                mBitmap = in.readParcelable(Bitmap.class.getClassLoader());
+                mBitmapBundle = in.readBundle(getClass().getClassLoader());
                 mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader());
                 mInsets = in.readParcelable(Insets.class.getClassLoader());
                 mTaskId = in.readInt();
+                mUserId = in.readInt();
+                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader());
             }
         }
 
@@ -81,8 +88,8 @@
             return mHasNavBar;
         }
 
-        public Bitmap getBitmap() {
-            return mBitmap;
+        public Bundle getBitmapBundle() {
+            return mBitmapBundle;
         }
 
         public Rect getBoundsInScreen() {
@@ -97,6 +104,15 @@
             return mTaskId;
         }
 
+
+        public int getUserId() {
+            return mUserId;
+        }
+
+        public ComponentName getTopComponent() {
+            return mTopComponent;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -107,14 +123,16 @@
             dest.writeInt(mSource);
             dest.writeBoolean(mHasStatusBar);
             dest.writeBoolean(mHasNavBar);
-            if (mBitmap == null) {
+            if (mBitmapBundle == null) {
                 dest.writeInt(0);
             } else {
                 dest.writeInt(1);
-                dest.writeParcelable(mBitmap, 0);
+                dest.writeBundle(mBitmapBundle);
                 dest.writeParcelable(mBoundsInScreen, 0);
                 dest.writeParcelable(mInsets, 0);
                 dest.writeInt(mTaskId);
+                dest.writeInt(mUserId);
+                dest.writeParcelable(mTopComponent, 0);
             }
         }
 
@@ -234,19 +252,22 @@
     /**
      * Request that provided image be handled as if it was a screenshot.
      *
-     * @param screenshot         The bitmap to treat as the screen shot.
+     * @param screenshotBundle   Bundle containing the buffer and color space of the screenshot.
      * @param boundsInScreen     The bounds in screen coordinates that the bitmap orginated from.
      * @param insets             The insets that the image was shown with, inside the screenbounds.
      * @param taskId             The taskId of the task that the screen shot was taken of.
+     * @param userId             The userId of user running the task provided in taskId.
+     * @param topComponent       The component name of the top component running in the task.
      * @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 provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen,
-            @NonNull Insets insets, int taskId, int source,
+    public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
+            @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source,
             @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
         ScreenshotRequest screenshotRequest =
-                new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId);
+                new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId,
+                        userId, topComponent);
         takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
                 handler, screenshotRequest, completionConsumer);
     }
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index c75f72b..0d2dbef 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -1223,7 +1223,6 @@
             mExpandButtonContainer.setVisibility(VISIBLE);
             mExpandButtonInnerContainer.setOnClickListener(onClickListener);
         } else {
-            // TODO: handle content paddings to end of layout
             mExpandButtonContainer.setVisibility(GONE);
         }
         updateContentEndPaddings();
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3d8cae8..5c444bd 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1744,6 +1744,8 @@
       heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
   }
   android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level));
+  // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
+  runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK;
 
   bool forceEnableGwpAsan = false;
   switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
@@ -1756,6 +1758,8 @@
       case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY:
           android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan));
   }
+  // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
+  runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK;
 
   if (NeedsNoRandomizeWorkaround()) {
     // Work around ARM kernel ASLR lossage (http://b/5817320).
diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto
index 23fcf6e..787074b 100644
--- a/core/proto/android/server/connectivity/data_stall_event.proto
+++ b/core/proto/android/server/connectivity/data_stall_event.proto
@@ -32,6 +32,7 @@
     AP_BAND_UNKNOWN = 0;
     AP_BAND_2GHZ = 1;
     AP_BAND_5GHZ = 2;
+    AP_BAND_6GHZ = 3;
 }
 
 // Refer to definition in TelephonyManager.java.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fd8460f..464a470 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3656,7 +3656,8 @@
          <p>The package installer v2 APIs are still a work in progress and we're
          currently validating they work in all scenarios.
          <p>Not for use by third-party applications.
-         TODO(b/152310230): remove this permission once the APIs are confirmed to be sufficient.
+         TODO(b/152310230): use this permission to protect only Incremental installations
+         once the APIs are confirmed to be sufficient.
          @hide
     -->
     <permission android:name="com.android.permission.USE_INSTALLER_V2"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index ec54091..3615b9e 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -22,10 +22,11 @@
         android:layout_gravity="bottom">
 
         <LinearLayout
+            android:id="@+id/actions_container_layout"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal"
-            android:paddingEnd="12dp"
+            android:paddingEnd="@dimen/bubble_gone_padding_end"
             >
 
             <com.android.internal.widget.NotificationActionListLayout
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c962256..f42b248 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1830,30 +1830,13 @@
         <!-- @hide no longer used, kept to preserve padding -->
         <attr name="allowAutoRevokePermissionsExemption" format="boolean" />
 
-        <!-- Declare the app's tolerance to having its permissions automatically revoked when unused for an extended
-             period of time -->
+        <!-- No longer used. Declaring this does nothing -->
         <attr name="autoRevokePermissions">
-            <!-- App supports re-requesting its permissions if revoked.
-                 Revoking app's permissions doesn't cause user experience issues, aside from a repeated permission request.
-
-                 Permissions may be automatically revoked from an app if unused. The app must check and possibly request the
-                 necessary permission on each permission-gated call-->
+            <!-- No longer used -->
             <enum name="allowed" value="0" />
-            <!-- App may experience degraded functionality when its previously-granted permissions are revoked.
-                 Revoking app's permissions may cause user experience issues, that are not critical to the user.
-
-                 Apps with this declaration can choose to request an exemption from auto revoke from user by starting
-                 an activity with {@code Intent.ACTION_AUTO_REVOKE_PERMISSIONS}. -->
+            <!-- No longer used -->
             <enum name="discouraged" value="1" />
-            <!-- User may experience severe consequences if this app's permissions are revoked unexpectedly.
-
-                 E.g. app may fail to do a user-critical background job that may likely impact user's
-                 safety/security/device accessibility.
-
-                 This declaration may cause an additional review when publishing your app.
-
-                 Apps with this declaration are exempt from auto revoke by default, though the user has the final say
-                 in both revoking the permissions as well as the app's auto revoke exemption status. -->
+            <!-- No longer used -->
             <enum name="disallowed" value="2" />
         </attr>
     </declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2cad9e6..b79c9e8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4456,11 +4456,4 @@
     <bool name="config_pdp_reject_enable_retry">false</bool>
     <!-- pdp data reject retry delay in ms -->
     <integer name="config_pdp_reject_retry_delay_ms">-1</integer>
-
-    <!-- Package name that is recognized as an actor for the packages listed in
-         @array/config_overlayableConfiguratorTargets. If an overlay targeting one of the listed
-         targets is signed with the same signature as the configurator, the overlay will be granted
-         the "actor" policy. -->
-    <string name="config_overlayableConfigurator" translatable="false" />
-    <string-array name="config_overlayableConfiguratorTargets" translatable="false" />
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ad3d20e..59bb052 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -312,6 +312,12 @@
     <!-- The margin of the content to an image-->
     <dimen name="notification_content_image_margin_end">8dp</dimen>
 
+    <!-- The padding at the end of actions when the bubble button is visible-->
+    <dimen name="bubble_visible_padding_end">3dp</dimen>
+
+    <!-- The padding at the end of actions when the bubble button is gone-->
+    <dimen name="bubble_gone_padding_end">12dp</dimen>
+
     <!-- The spacing between messages in Notification.MessagingStyle -->
     <dimen name="notification_messaging_spacing">6dp</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f30d482e..f3345b0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2766,6 +2766,7 @@
   <java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" />
 
   <java-symbol type="id" name="actions_container" />
+  <java-symbol type="id" name="actions_container_layout" />
   <java-symbol type="id" name="smart_reply_container" />
   <java-symbol type="id" name="remote_input_tag" />
   <java-symbol type="id" name="pending_intent_tag" />
@@ -3533,6 +3534,8 @@
   <java-symbol type="id" name="clip_to_padding_tag" />
   <java-symbol type="id" name="clip_children_tag" />
   <java-symbol type="id" name="bubble_button" />
+  <java-symbol type="dimen" name="bubble_visible_padding_end" />
+  <java-symbol type="dimen" name="bubble_gone_padding_end" />
   <java-symbol type="dimen" name="messaging_avatar_size" />
   <java-symbol type="dimen" name="messaging_group_sending_progress_size" />
   <java-symbol type="dimen" name="messaging_image_rounding" />
@@ -4033,8 +4036,5 @@
   <java-symbol type="string" name="config_pdp_reject_service_not_subscribed" />
   <java-symbol type="string" name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" />
 
-  <java-symbol type="string" name="config_overlayableConfigurator" />
-  <java-symbol type="array" name="config_overlayableConfiguratorTargets" />
-
   <java-symbol type="array" name="config_notificationMsgPkgsAllowedAsConvos" />
 </resources>
diff --git a/core/tests/PackageInstallerSessions/Android.bp b/core/tests/PackageInstallerSessions/Android.bp
new file mode 100644
index 0000000..e74f30e
--- /dev/null
+++ b/core/tests/PackageInstallerSessions/Android.bp
@@ -0,0 +1,42 @@
+//
+// 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.
+//
+
+android_test {
+    name: "FrameworksCorePackageInstallerSessionsTests",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "frameworks-base-testutils",
+        "platform-test-annotations",
+        "testng",
+        "truth-prebuilt",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "framework",
+        "framework-res",
+    ],
+
+    platform_apis: true,
+    sdk_version: "core_platform",
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/PackageInstallerSessions/AndroidManifest.xml b/core/tests/PackageInstallerSessions/AndroidManifest.xml
new file mode 100644
index 0000000..5b22d2b
--- /dev/null
+++ b/core/tests/PackageInstallerSessions/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.frameworks.coretests.package_installer_sessions"
+    >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.coretests.package_installer_sessions"/>
+</manifest>
diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
new file mode 100644
index 0000000..494c92a
--- /dev/null
+++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm
+
+import android.content.Context
+import android.content.pm.PackageInstaller.SessionParams
+import android.platform.test.annotations.Presubmit
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.LargeTest
+import com.android.compatibility.common.util.ShellIdentityUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.testng.Assert.assertThrows
+import kotlin.random.Random
+
+/**
+ * For verifying public [PackageInstaller] session APIs. This differs from
+ * [com.android.server.pm.PackageInstallerSessionTest] in services because that mocks the session,
+ * whereas this test uses the installer on device.
+ */
+@Presubmit
+class PackageSessionTests {
+
+    companion object {
+        /**
+         * Permissions marked "hardRestricted" or "softRestricted" in core/res/AndroidManifest.xml.
+         */
+        private val RESTRICTED_PERMISSIONS = listOf(
+                "android.permission.SEND_SMS",
+                "android.permission.RECEIVE_SMS",
+                "android.permission.READ_SMS",
+                "android.permission.RECEIVE_WAP_PUSH",
+                "android.permission.RECEIVE_MMS",
+                "android.permission.READ_CELL_BROADCASTS",
+                "android.permission.ACCESS_BACKGROUND_LOCATION",
+                "android.permission.READ_CALL_LOG",
+                "android.permission.WRITE_CALL_LOG",
+                "android.permission.PROCESS_OUTGOING_CALLS"
+        )
+    }
+
+    private val context: Context = InstrumentationRegistry.getContext()
+
+    private val installer = context.packageManager.packageInstaller
+
+    @Before
+    @After
+    fun abandonAllSessions() {
+        installer.mySessions.asSequence()
+                .map { it.sessionId }
+                .forEach {
+                    try {
+                        installer.abandonSession(it)
+                    } catch (ignored: Exception) {
+                        // Querying for sessions checks by calling package name, but abandoning
+                        // checks by UID, which won't match if this test failed to clean up
+                        // on a previous install + run + uninstall, so ignore these failures.
+                    }
+                }
+    }
+
+    @Test
+    fun truncateAppLabel() {
+        val longLabel = invalidAppLabel()
+        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
+            setAppLabel(longLabel)
+        }
+
+        createSession(params) {
+            assertThat(installer.getSessionInfo(it)?.appLabel)
+                    .isEqualTo(longLabel.take(PackageItemInfo.MAX_SAFE_LABEL_LENGTH))
+        }
+    }
+
+    @Test
+    fun removeInvalidAppPackageName() {
+        val longName = invalidPackageName()
+        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
+            setAppPackageName(longName)
+        }
+
+        createSession(params) {
+            assertThat(installer.getSessionInfo(it)?.appPackageName)
+                    .isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun removeInvalidInstallerPackageName() {
+        val longName = invalidPackageName()
+        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
+            setInstallerPackageName(longName)
+        }
+
+        createSession(params) {
+            // If a custom installer name is dropped, it defaults to the caller
+            assertThat(installer.getSessionInfo(it)?.installerPackageName)
+                    .isEqualTo(context.packageName)
+        }
+    }
+
+    @Test
+    fun truncateWhitelistPermissions() {
+        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
+            setWhitelistedRestrictedPermissions(invalidPermissions())
+        }
+
+        createSession(params) {
+            assertThat(installer.getSessionInfo(it)?.whitelistedRestrictedPermissions!!)
+                    .containsExactlyElementsIn(RESTRICTED_PERMISSIONS)
+        }
+    }
+
+    @LargeTest
+    @Test
+    fun allocateMaxSessionsWithPermission() {
+        ShellIdentityUtils.invokeWithShellPermissions {
+            repeat(1024) { createDummySession() }
+            assertThrows(IllegalStateException::class.java) { createDummySession() }
+        }
+    }
+
+    @LargeTest
+    @Test
+    fun allocateMaxSessionsNoPermission() {
+        repeat(50) { createDummySession() }
+        assertThrows(IllegalStateException::class.java) { createDummySession() }
+    }
+
+    private fun createDummySession() {
+        installer.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL)
+                .apply {
+                    setAppPackageName(invalidPackageName())
+                    setAppLabel(invalidAppLabel())
+                    setWhitelistedRestrictedPermissions(invalidPermissions())
+                })
+    }
+
+    private fun invalidPackageName(maxLength: Int = SessionParams.MAX_PACKAGE_NAME_LENGTH): String {
+        return (0 until (maxLength + 10))
+                .asSequence()
+                .mapIndexed { index, _ ->
+                    // A package name needs at least one separator
+                    if (index == 2) {
+                        '.'
+                    } else {
+                        Random.nextInt('z' - 'a').toChar() + 'a'.toInt()
+                    }
+                }
+                .joinToString(separator = "")
+    }
+
+    private fun invalidAppLabel() = (0 until PackageItemInfo.MAX_SAFE_LABEL_LENGTH + 10)
+            .asSequence()
+            .map { Random.nextInt(Char.MAX_VALUE.toInt()).toChar() }
+            .joinToString(separator = "")
+
+    private fun invalidPermissions() = RESTRICTED_PERMISSIONS.toMutableSet()
+            .apply {
+                // Add some invalid permission names
+                repeat(10) { add(invalidPackageName(300)) }
+            }
+
+    private fun createSession(params: SessionParams, block: (Int) -> Unit = {}) {
+        val sessionId = installer.createSession(params)
+        try {
+            block(sessionId)
+        } finally {
+            installer.abandonSession(sessionId)
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index f13a11e..17d1389 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -23,12 +23,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.app.ActivityThread;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
+import android.inputmethodservice.InputMethodService;
 import android.media.ImageReader;
 import android.os.UserHandle;
 import android.view.Display;
@@ -136,6 +138,13 @@
     }
 
     @Test
+    public void testIsUiContext_InputMethodService_returnsTrue() {
+        final InputMethodService ims = new InputMethodService();
+
+        assertTrue(ims.isUiContext());
+    }
+
+    @Test
     public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
         verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
     }
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 7cd2f3b..a4f2065 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -363,6 +363,16 @@
         public boolean isHdmiCecVolumeControlEnabled() {
             return true;
         }
+
+        @Override
+        public void addHdmiCecVolumeControlFeatureListener(
+                IHdmiCecVolumeControlFeatureListener listener) {
+        }
+
+        @Override
+        public void removeHdmiCecVolumeControlFeatureListener(
+                IHdmiCecVolumeControlFeatureListener listener) {
+        }
     }
 
 }
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 fe33cd8..4b81737 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -29,11 +29,12 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.WindowManager;
@@ -91,8 +92,7 @@
     @Test
     public void testProvidedImageScreenshot() {
         mScreenshotHelper.provideScreenshot(
-                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(),
-                Insets.of(0, 0, 0, 0), 1,
+                new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
                 WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
     }
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e00813c..9b503eb 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -378,6 +378,9 @@
         <permission name="android.permission.SET_WALLPAPER" />
         <permission name="android.permission.SET_WALLPAPER_COMPONENT" />
         <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
+        <!-- Permissions required for Incremental CTS tests -->
+        <permission name="com.android.permission.USE_INSTALLER_V2"/>
+        <permission name="android.permission.LOADER_USAGE_STATS"/>
         <!-- Permission required to test system only camera devices. -->
         <permission name="android.permission.SYSTEM_CAMERA" />
         <!-- Permission required to test ExplicitHealthCheckServiceImpl. -->
@@ -418,6 +421,8 @@
         <permission name="android.permission.TV_INPUT_HARDWARE" />
         <!-- Permission required for CTS test - PrivilegedLocationPermissionTest -->
         <permission name="android.permission.LOCATION_HARDWARE" />
+        <!-- Permissions required for GTS test - GtsDialerAudioTestCases -->
+        <permission name="android.permission.CAPTURE_AUDIO_OUTPUT" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/keyboards/Vendor_045e_Product_0b12.kl b/data/keyboards/Vendor_045e_Product_0b12.kl
new file mode 100644
index 0000000..0b44c743
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_0b12.kl
@@ -0,0 +1,59 @@
+# 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.
+
+#
+# XBox USB Controller
+#
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 307   BUTTON_X
+key 308   BUTTON_Y
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+# Left and right stick.
+# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
+# This confuses applications that rely on the flat value because the joystick actually
+# settles in a flat range of +/- 4096 or so.
+axis 0x00 X flat 4096
+axis 0x01 Y flat 4096
+axis 0x03 Z flat 4096
+axis 0x04 RZ flat 4096
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+
+# Two overlapping rectangles
+key 314   BUTTON_SELECT
+
+# The branded "X" button in the center of the controller
+key 316   BUTTON_MODE
+
+# Three parallel horizontal lines (hamburger menu)
+key 315   BUTTON_START
+
+#Button below the "X" button
+key 167   MEDIA_RECORD
+
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index c652628..590def4 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.hardware.cas.V1_0.HidlCasPluginDescriptor;
 import android.hardware.cas.V1_0.ICas;
@@ -43,6 +44,8 @@
 import android.util.Log;
 import android.util.Singleton;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -122,6 +125,7 @@
     private String mTvInputServiceSessionId;
     private int mClientId;
     private int mCasSystemId;
+    private int mUserId;
     private TunerResourceManager mTunerResourceManager = null;
     private final Map<Session, Integer> mSessionMap = new HashMap<>();
 
@@ -673,6 +677,8 @@
      */
     public MediaCas(int CA_system_id) throws UnsupportedCasException {
         try {
+            mCasSystemId = CA_system_id;
+            mUserId = ActivityManager.getCurrentUser();
             IMediaCasService service = getService();
             android.hardware.cas.V1_2.IMediaCasService serviceV12 =
                     android.hardware.cas.V1_2.IMediaCasService.castFrom(service);
@@ -721,7 +727,6 @@
         this(casSystemId);
 
         Objects.requireNonNull(context, "context must not be null");
-        mCasSystemId = casSystemId;
         mTunerResourceManager = (TunerResourceManager)
                 context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
         if (mTunerResourceManager != null) {
@@ -925,10 +930,18 @@
             mICas.openSession(cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
             addSessionToResourceMap(cb.mSession, sessionResourceHandle);
+            Log.d(TAG, "Write Stats Log for succeed to Open Session.");
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
+                        FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
+        Log.d(TAG, "Write Stats Log for fail to Open Session.");
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
+                    FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED);
         return null;
     }
 
@@ -964,10 +977,18 @@
             mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
             addSessionToResourceMap(cb.mSession, sessionResourceHandle);
+            Log.d(TAG, "Write Stats Log for succeed to Open Session.");
+            FrameworkStatsLog
+                    .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
+                        FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
+        Log.d(TAG, "Write Stats Log for fail to Open Session.");
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
+                    FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED);
         return null;
     }
 
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 981bf7a..05c6e3a 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -137,7 +137,7 @@
     private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
     private MediaRoute2ProviderServiceStub mStub;
     private IMediaRoute2ProviderServiceCallback mRemoteCallback;
-    private MediaRoute2ProviderInfo mProviderInfo;
+    private volatile MediaRoute2ProviderInfo mProviderInfo;
 
     @GuardedBy("mSessionLock")
     private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
@@ -167,8 +167,8 @@
     /**
      * Called when a volume setting is requested on a route of the provider
      *
-     * @param requestId the id of this request
-     * @param routeId the id of the route
+     * @param requestId the ID of this request
+     * @param routeId the ID of the route
      * @param volume the target volume
      * @see MediaRoute2Info.Builder#setVolume(int)
      */
@@ -178,8 +178,8 @@
      * Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on
      * a routing session of the provider
      *
-     * @param requestId the id of this request
-     * @param sessionId the id of the routing session
+     * @param requestId the ID of this request
+     * @param sessionId the ID of the routing session
      * @param volume the target volume
      * @see RoutingSessionInfo.Builder#setVolume(int)
      */
@@ -188,7 +188,7 @@
     /**
      * Gets information of the session with the given id.
      *
-     * @param sessionId id of the session
+     * @param sessionId the ID of the session
      * @return information of the session with the given id.
      *         null if the session is released or ID is not valid.
      */
@@ -218,7 +218,7 @@
      * If this session is created without any creation request, use {@link #REQUEST_ID_NONE}
      * as the request ID.
      *
-     * @param requestId id of the previous request to create this session provided in
+     * @param requestId the ID of the previous request to create this session provided in
      *                  {@link #onCreateSession(long, String, String, Bundle)}. Can be
      *                  {@link #REQUEST_ID_NONE} if this session is created without any request.
      * @param sessionInfo information of the new session.
@@ -237,18 +237,15 @@
                 return;
             }
             mSessionInfo.put(sessionInfo.getId(), sessionInfo);
-        }
 
-        if (mRemoteCallback == null) {
-            return;
-        }
-        try {
-            // TODO(b/157873487): Calling binder calls in multiple thread may cause timing issue.
-            //       Consider to change implementations to avoid the problems.
-            //       For example, post binder calls, always send all sessions at once, etc.
-            mRemoteCallback.notifySessionCreated(requestId, sessionInfo);
-        } catch (RemoteException ex) {
-            Log.w(TAG, "Failed to notify session created.");
+            if (mRemoteCallback == null) {
+                return;
+            }
+            try {
+                mRemoteCallback.notifySessionCreated(requestId, sessionInfo);
+            } catch (RemoteException ex) {
+                Log.w(TAG, "Failed to notify session created.");
+            }
         }
     }
 
@@ -267,22 +264,22 @@
                 Log.w(TAG, "Ignoring unknown session info.");
                 return;
             }
-        }
 
-        if (mRemoteCallback == null) {
-            return;
-        }
-        try {
-            mRemoteCallback.notifySessionUpdated(sessionInfo);
-        } catch (RemoteException ex) {
-            Log.w(TAG, "Failed to notify session info changed.");
+            if (mRemoteCallback == null) {
+                return;
+            }
+            try {
+                mRemoteCallback.notifySessionUpdated(sessionInfo);
+            } catch (RemoteException ex) {
+                Log.w(TAG, "Failed to notify session info changed.");
+            }
         }
     }
 
     /**
      * Notifies that the session is released.
      *
-     * @param sessionId id of the released session.
+     * @param sessionId the ID of the released session.
      * @see #onReleaseSession(long, String)
      */
     public final void notifySessionReleased(@NonNull String sessionId) {
@@ -292,20 +289,20 @@
         RoutingSessionInfo sessionInfo;
         synchronized (mSessionLock) {
             sessionInfo = mSessionInfo.remove(sessionId);
-        }
 
-        if (sessionInfo == null) {
-            Log.w(TAG, "Ignoring unknown session info.");
-            return;
-        }
+            if (sessionInfo == null) {
+                Log.w(TAG, "Ignoring unknown session info.");
+                return;
+            }
 
-        if (mRemoteCallback == null) {
-            return;
-        }
-        try {
-            mRemoteCallback.notifySessionReleased(sessionInfo);
-        } catch (RemoteException ex) {
-            Log.w(TAG, "Failed to notify session info changed.");
+            if (mRemoteCallback == null) {
+                return;
+            }
+            try {
+                mRemoteCallback.notifySessionReleased(sessionInfo);
+            } catch (RemoteException ex) {
+                Log.w(TAG, "Failed to notify session info changed.");
+            }
         }
     }
 
@@ -348,9 +345,9 @@
      * If you can't create the session or want to reject the request, call
      * {@link #notifyRequestFailed(long, int)} with the given {@code requestId}.
      *
-     * @param requestId the id of this request
+     * @param requestId the ID of this request
      * @param packageName the package name of the application that selected the route
-     * @param routeId the id of the route initially being connected
+     * @param routeId the ID of the route initially being connected
      * @param sessionHints an optional bundle of app-specific arguments sent by
      *                     {@link MediaRouter2}, or null if none. The contents of this bundle
      *                     may affect the result of session creation.
@@ -372,8 +369,8 @@
      * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger
      * this method to be called.
      *
-     * @param requestId the id of this request
-     * @param sessionId id of the session being released.
+     * @param requestId the ID of this request
+     * @param sessionId the ID of the session being released.
      * @see #notifySessionReleased(String)
      * @see #getSessionInfo(String)
      */
@@ -384,9 +381,9 @@
      * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
      * to update session info.
      *
-     * @param requestId the id of this request
-     * @param sessionId id of the session
-     * @param routeId id of the route
+     * @param requestId the ID of this request
+     * @param sessionId the ID of the session
+     * @param routeId the ID of the route
      */
     public abstract void onSelectRoute(long requestId, @NonNull String sessionId,
             @NonNull String routeId);
@@ -396,9 +393,9 @@
      * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
      * to update session info.
      *
-     * @param requestId the id of this request
-     * @param sessionId id of the session
-     * @param routeId id of the route
+     * @param requestId the ID of this request
+     * @param sessionId the ID of the session
+     * @param routeId the ID of the route
      */
     public abstract void onDeselectRoute(long requestId, @NonNull String sessionId,
             @NonNull String routeId);
@@ -408,9 +405,9 @@
      * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)}
      * to update session info.
      *
-     * @param requestId the id of this request
-     * @param sessionId id of the session
-     * @param routeId id of the route
+     * @param requestId the ID of this request
+     * @param sessionId the ID of the session
+     * @param routeId the ID of the route
      */
     public abstract void onTransferToRoute(long requestId, @NonNull String sessionId,
             @NonNull String routeId);
@@ -475,13 +472,39 @@
     final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub {
         MediaRoute2ProviderServiceStub() { }
 
-        boolean checkCallerisSystem() {
+        private boolean checkCallerIsSystem() {
             return Binder.getCallingUid() == Process.SYSTEM_UID;
         }
 
+        private boolean checkSessionIdIsValid(String sessionId, String description) {
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, description + ": Ignoring empty sessionId from system service.");
+                return false;
+            }
+            if (getSessionInfo(sessionId) == null) {
+                Log.w(TAG, description + ": Ignoring unknown session from system service. "
+                        + "sessionId=" + sessionId);
+                return false;
+            }
+            return true;
+        }
+
+        private boolean checkRouteIdIsValid(String routeId, String description) {
+            if (TextUtils.isEmpty(routeId)) {
+                Log.w(TAG, description + ": Ignoring empty routeId from system service.");
+                return false;
+            }
+            if (mProviderInfo == null || mProviderInfo.getRoute(routeId) == null) {
+                Log.w(TAG, description + ": Ignoring unknown route from system service. "
+                        + "routeId=" + routeId);
+                return false;
+            }
+            return true;
+        }
+
         @Override
         public void setCallback(IMediaRoute2ProviderServiceCallback callback) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setCallback,
@@ -490,7 +513,7 @@
 
         @Override
         public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(
@@ -500,7 +523,10 @@
 
         @Override
         public void setRouteVolume(long requestId, String routeId, int volume) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
+                return;
+            }
+            if (!checkRouteIdIsValid(routeId, "setRouteVolume")) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume,
@@ -510,7 +536,10 @@
         @Override
         public void requestCreateSession(long requestId, String packageName, String routeId,
                 @Nullable Bundle requestCreateSession) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
+                return;
+            }
+            if (!checkRouteIdIsValid(routeId, "requestCreateSession")) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
@@ -518,14 +547,13 @@
                     requestCreateSession));
         }
 
-        //TODO(b/157873546): Ignore requests with unknown session ID. -> For all similar commands.
         @Override
         public void selectRoute(long requestId, String sessionId, String routeId) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
                 return;
             }
-            if (TextUtils.isEmpty(sessionId)) {
-                Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service.");
+            if (!checkSessionIdIsValid(sessionId, "selectRoute")
+                    || !checkRouteIdIsValid(routeId, "selectRoute")) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
@@ -534,11 +562,11 @@
 
         @Override
         public void deselectRoute(long requestId, String sessionId, String routeId) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
                 return;
             }
-            if (TextUtils.isEmpty(sessionId)) {
-                Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service.");
+            if (!checkSessionIdIsValid(sessionId, "deselectRoute")
+                    || !checkRouteIdIsValid(routeId, "deselectRoute")) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
@@ -547,11 +575,11 @@
 
         @Override
         public void transferToRoute(long requestId, String sessionId, String routeId) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
                 return;
             }
-            if (TextUtils.isEmpty(sessionId)) {
-                Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service.");
+            if (!checkSessionIdIsValid(sessionId, "transferToRoute")
+                    || !checkRouteIdIsValid(routeId, "transferToRoute")) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
@@ -560,7 +588,10 @@
 
         @Override
         public void setSessionVolume(long requestId, String sessionId, int volume) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
+                return;
+            }
+            if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume,
@@ -569,11 +600,10 @@
 
         @Override
         public void releaseSession(long requestId, String sessionId) {
-            if (!checkCallerisSystem()) {
+            if (!checkCallerIsSystem()) {
                 return;
             }
-            if (TextUtils.isEmpty(sessionId)) {
-                Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service.");
+            if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
index 68071b0..bb00bb3 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
@@ -20,12 +20,16 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.app.ActivityManager;
 import android.hardware.tv.tuner.V1_0.Constants;
 import android.media.tv.tuner.Tuner;
 import android.media.tv.tuner.Tuner.Result;
 import android.media.tv.tuner.TunerUtils;
 import android.media.tv.tuner.filter.Filter;
 import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -72,9 +76,15 @@
      */
     public static final int PLAYBACK_STATUS_FULL = Constants.PlaybackStatus.SPACE_FULL;
 
+    private static final String TAG = "TvTunerPlayback";
+
     private long mNativeContext;
     private OnPlaybackStatusChangedListener mListener;
     private Executor mExecutor;
+    private int mUserId;
+    private static int sInstantId = 0;
+    private int mSegmentId = 0;
+    private int mUnderflow;
 
     private native int nativeAttachFilter(Filter filter);
     private native int nativeDetachFilter(Filter filter);
@@ -88,6 +98,9 @@
     private native long nativeRead(byte[] bytes, long offset, long size);
 
     private DvrPlayback() {
+        mUserId = ActivityManager.getCurrentUser();
+        mSegmentId = (sInstantId & 0x0000ffff) << 16;
+        sInstantId++;
     }
 
     /** @hide */
@@ -98,6 +111,9 @@
     }
 
     private void onPlaybackStatusChanged(int status) {
+        if (status == PLAYBACK_STATUS_EMPTY) {
+            mUnderflow++;
+        }
         if (mExecutor != null && mListener != null) {
             mExecutor.execute(() -> mListener.onPlaybackStatusChanged(status));
         }
@@ -154,6 +170,13 @@
      */
     @Result
     public int start() {
+        mSegmentId =  (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff);
+        mUnderflow = 0;
+        Log.d(TAG, "Write Stats Log for Playback.");
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__PLAYBACK,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0);
         return nativeStartDvr();
     }
 
@@ -167,6 +190,11 @@
      */
     @Result
     public int stop() {
+        Log.d(TAG, "Write Stats Log for Playback.");
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__PLAYBACK,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mUnderflow);
         return nativeStopDvr();
     }
 
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 198bd0f..8871167 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -19,14 +19,19 @@
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.app.ActivityManager;
 import android.media.tv.tuner.Tuner;
 import android.media.tv.tuner.Tuner.Result;
 import android.media.tv.tuner.TunerUtils;
 import android.media.tv.tuner.filter.Filter;
 import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.util.concurrent.Executor;
 
+
 /**
  * Digital Video Record (DVR) recorder class which provides record control on Demux's output buffer.
  *
@@ -34,9 +39,14 @@
  */
 @SystemApi
 public class DvrRecorder implements AutoCloseable {
+    private static final String TAG = "TvTunerRecord";
     private long mNativeContext;
     private OnRecordStatusChangedListener mListener;
     private Executor mExecutor;
+    private int mUserId;
+    private static int sInstantId = 0;
+    private int mSegmentId = 0;
+    private int mOverflow;
 
     private native int nativeAttachFilter(Filter filter);
     private native int nativeDetachFilter(Filter filter);
@@ -50,6 +60,9 @@
     private native long nativeWrite(byte[] bytes, long offset, long size);
 
     private DvrRecorder() {
+        mUserId = ActivityManager.getCurrentUser();
+        mSegmentId = (sInstantId & 0x0000ffff) << 16;
+        sInstantId++;
     }
 
     /** @hide */
@@ -60,6 +73,9 @@
     }
 
     private void onRecordStatusChanged(int status) {
+        if (status == Filter.STATUS_OVERFLOW) {
+            mOverflow++;
+        }
         if (mExecutor != null && mListener != null) {
             mExecutor.execute(() -> mListener.onRecordStatusChanged(status));
         }
@@ -112,6 +128,13 @@
      */
     @Result
     public int start() {
+        mSegmentId =  (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff);
+        mOverflow = 0;
+        Log.d(TAG, "Write Stats Log for Record.");
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0);
         return nativeStartDvr();
     }
 
@@ -124,6 +147,11 @@
      */
     @Result
     public int stop() {
+        Log.d(TAG, "Write Stats Log for Playback.");
+        FrameworkStatsLog
+                .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
+                    FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow);
         return nativeStopDvr();
     }
 
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 2a715d0..9317498 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -29,9 +29,10 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:paddingStart="20dp"
+        android:gravity="center"
+        android:layoutDirection="ltr"
         android:paddingEnd="20dp"
-        android:gravity="center">
+        android:paddingStart="20dp">
 
         <com.android.systemui.car.navigationbar.CarNavigationButton
             android:id="@+id/home"
@@ -135,9 +136,10 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:paddingStart="@dimen/car_keyline_1"
-        android:paddingEnd="@dimen/car_keyline_1"
         android:gravity="center"
+        android:layoutDirection="ltr"
+        android:paddingEnd="@dimen/car_keyline_1"
+        android:paddingStart="@dimen/car_keyline_1"
         android:visibility="gone"
     />
 
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
index 60e0d7e..cdc29ee 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -27,7 +27,8 @@
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="1">
+        android:layout_weight="1"
+        android:layoutDirection="ltr">
 
         <FrameLayout
             android:id="@+id/left_hvac_container"
diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml
index 76d106a..8f5b2ed 100644
--- a/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml
+++ b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml
@@ -18,4 +18,8 @@
     <color name="homepage_generic_icon_background">#1A73E8</color>
 
     <color name="bt_outline_color">#1f000000</color> <!-- icon outline color -->
+
+    <color name="advanced_outline_color">#BDC1C6</color> <!-- icon outline color -->
+
+    <color name="advanced_icon_color">#3C4043</color>
 </resources>
diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml
index 7f5b58c..8f6e358 100644
--- a/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml
+++ b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml
@@ -18,6 +18,8 @@
 <resources>
     <!-- Dashboard foreground image inset (from background edge to foreground edge) -->
     <dimen name="dashboard_tile_foreground_image_inset">6dp</dimen>
+    <!-- Advanced dashboard foreground image inset (from background edge to foreground edge) -->
+    <dimen name="advanced_dashboard_tile_foreground_image_inset">9dp</dimen>
 
     <!-- Stroke size of adaptive outline -->
     <dimen name="adaptive_outline_stroke">1dp</dimen>
diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java
index 1c65bc2..4438893 100644
--- a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java
+++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java
@@ -16,6 +16,10 @@
 
 package com.android.settingslib.widget;
 
+import static com.android.settingslib.widget.AdaptiveOutlineDrawable.AdaptiveOutlineIconType.TYPE_ADVANCED;
+import static com.android.settingslib.widget.AdaptiveOutlineDrawable.AdaptiveOutlineIconType.TYPE_DEFAULT;
+
+import android.annotation.ColorInt;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -27,35 +31,90 @@
 import android.graphics.drawable.DrawableWrapper;
 import android.util.PathParser;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Adaptive outline drawable with white plain background color and black outline
  */
 public class AdaptiveOutlineDrawable extends DrawableWrapper {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_DEFAULT, TYPE_ADVANCED})
+    public @interface AdaptiveOutlineIconType {
+        int TYPE_DEFAULT = 0;
+        int TYPE_ADVANCED = 1;
+    }
+
     @VisibleForTesting
-    final Paint mOutlinePaint;
+    Paint mOutlinePaint;
     private Path mPath;
-    private final int mInsetPx;
-    private final Bitmap mBitmap;
+    private int mInsetPx;
+    private int mStrokeWidth;
+    private Bitmap mBitmap;
+    private int mType;
 
     public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap) {
         super(new AdaptiveIconShapeDrawable(resources));
 
+        init(resources, bitmap, TYPE_DEFAULT);
+    }
+
+    public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap,
+            @AdaptiveOutlineIconType int type) {
+        super(new AdaptiveIconShapeDrawable(resources));
+
+        init(resources, bitmap, type);
+    }
+
+    private void init(Resources resources, Bitmap bitmap,
+            @AdaptiveOutlineIconType int type) {
+        mType = type;
         getDrawable().setTint(Color.WHITE);
         mPath = new Path(PathParser.createPathFromPathData(
                 resources.getString(com.android.internal.R.string.config_icon_mask)));
+        mStrokeWidth = resources.getDimensionPixelSize(R.dimen.adaptive_outline_stroke);
         mOutlinePaint = new Paint();
-        mOutlinePaint.setColor(resources.getColor(R.color.bt_outline_color, null));
+        mOutlinePaint.setColor(getColor(resources, type));
         mOutlinePaint.setStyle(Paint.Style.STROKE);
-        mOutlinePaint.setStrokeWidth(resources.getDimension(R.dimen.adaptive_outline_stroke));
+        mOutlinePaint.setStrokeWidth(mStrokeWidth);
         mOutlinePaint.setAntiAlias(true);
 
-        mInsetPx = resources
-                .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset);
+        mInsetPx = getDimensionPixelSize(resources, type);
         mBitmap = bitmap;
     }
 
+    private @ColorInt int getColor(Resources resources, @AdaptiveOutlineIconType int type) {
+        int resId;
+        switch (type) {
+            case TYPE_ADVANCED:
+                resId = R.color.advanced_outline_color;
+                break;
+            case TYPE_DEFAULT:
+            default:
+                resId = R.color.bt_outline_color;
+                break;
+        }
+        return resources.getColor(resId, /* theme */ null);
+    }
+
+    private int getDimensionPixelSize(Resources resources, @AdaptiveOutlineIconType int type) {
+        int resId;
+        switch (type) {
+            case TYPE_ADVANCED:
+                resId = R.dimen.advanced_dashboard_tile_foreground_image_inset;
+                break;
+            case TYPE_DEFAULT:
+            default:
+                resId = R.dimen.dashboard_tile_foreground_image_inset;
+                break;
+        }
+        return resources.getDimensionPixelSize(resId);
+    }
+
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
@@ -68,7 +127,12 @@
         final int count = canvas.save();
         canvas.scale(scaleX, scaleY);
         // Draw outline
-        canvas.drawPath(mPath, mOutlinePaint);
+        if (mType == TYPE_DEFAULT) {
+            canvas.drawPath(mPath, mOutlinePaint);
+        } else {
+            canvas.drawCircle(2 * mInsetPx, 2 * mInsetPx, 2 * mInsetPx - mStrokeWidth,
+                    mOutlinePaint);
+        }
         canvas.restoreToCount(count);
 
         // Draw the foreground icon
diff --git a/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml b/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml
index fa5f9bd..2c9aaa5 100644
--- a/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="search_menu" msgid="1914043873178389845">"جستجوی تنظیمات"</string>
+    <string name="search_menu" msgid="1914043873178389845">"تنظیمات جستجو"</string>
 </resources>
diff --git a/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml b/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml
index 111cf5a..14b7b2f 100644
--- a/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="search_menu" msgid="1914043873178389845">"Mga setting ng paghahanap"</string>
+    <string name="search_menu" msgid="1914043873178389845">"Maghanap sa mga setting"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index d039c9f..1b5062e 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -140,8 +140,8 @@
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Адкрытая сетка"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Бяспечная сетка"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"АС Android"</string>
-    <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Выдаленыя прыкладанні"</string>
-    <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Выдаленыя прыкладанні і карыстальнiкi"</string>
+    <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Выдаленыя праграмы"</string>
+    <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Выдаленыя праграмы і карыстальнiкi"</string>
     <string name="data_usage_ota" msgid="7984667793701597001">"Абнаўленні сістэмы"</string>
     <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB-мадэм"</string>
     <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Партатыўны хот-спот"</string>
diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml
index a131a3b..b19cde4 100644
--- a/packages/SettingsLib/res/values-bn/arrays.xml
+++ b/packages/SettingsLib/res/values-bn/arrays.xml
@@ -40,7 +40,7 @@
     <item msgid="8339720953594087771">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> এর সাথে কানেক্ট হচ্ছে…"</item>
     <item msgid="3028983857109369308">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> দিয়ে যাচাইকরণ করা হচ্ছে..."</item>
     <item msgid="4287401332778341890">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> থেকে আইপি অ্যাড্রেস জানা হচ্ছে…"</item>
-    <item msgid="1043944043827424501">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> তে কানেক্ট হয়েছে"</item>
+    <item msgid="1043944043827424501">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>-এ কানেক্ট হয়েছে"</item>
     <item msgid="7445993821842009653">"স্থগিত করা হয়েছে"</item>
     <item msgid="1175040558087735707">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> থেকে ডিসকানেক্ট হচ্ছে…"</item>
     <item msgid="699832486578171722">"ডিসকানেক্ট করা হয়েছে"</item>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 8db0b7e..2644cb9 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -194,7 +194,7 @@
     <item msgid="581904787661470707">"Ταχύτατη"</item>
   </string-array>
     <string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</string>
-    <string name="category_personal" msgid="6236798763159385225">"Προσωπικός"</string>
+    <string name="category_personal" msgid="6236798763159385225">"Προσωπικό"</string>
     <string name="category_work" msgid="4014193632325996115">"Εργασίας"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Επιλογές για προγραμματιστές"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 65f456e..d003ef0 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -449,7 +449,7 @@
     <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täislaadimiseni"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string>
-    <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiiresti laadimine"</string>
+    <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string>
     <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Aeglaselt laadimine"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string>
     <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Vooluvõrgus, praegu ei saa laadida"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index e73febc..d20bf38 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -40,7 +40,7 @@
     <item msgid="8339720953594087771">"Menyambung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
     <item msgid="3028983857109369308">"Mengautentikasi dengan <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
     <item msgid="4287401332778341890">"Mendapatkan alamat IP dari <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
-    <item msgid="1043944043827424501">"Tersambung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
+    <item msgid="1043944043827424501">"Terhubung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
     <item msgid="7445993821842009653">"Ditangguhkan"</item>
     <item msgid="1175040558087735707">"Diputus dari <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
     <item msgid="699832486578171722">"Sambungan terputus"</item>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index a5ec5e6..e552d78 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -94,4 +94,7 @@
 
     <!-- Define minimal size of the tap area -->
     <dimen name="min_tap_target_size">48dp</dimen>
+
+    <!-- Size of advanced icon -->
+    <dimen name="advanced_icon_size">18dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index a6202956..38eeda2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -19,10 +19,13 @@
 import android.app.Application;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.hardware.usb.IUsbManager;
+import android.net.Uri;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -44,6 +47,15 @@
      */
     private static InstantAppDataProvider sInstantAppDataProvider = null;
 
+    private static final Intent sBrowserIntent;
+
+    static {
+        sBrowserIntent = new Intent()
+                .setAction(Intent.ACTION_VIEW)
+                .addCategory(Intent.CATEGORY_BROWSABLE)
+                .setData(Uri.parse("http:"));
+    }
+
     public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry,
             IUsbManager usbManager, PackageManager pm, Context context) {
         String packageName = appEntry.info.packageName;
@@ -153,4 +165,22 @@
         return com.android.settingslib.utils.applications.AppUtils.getAppContentDescription(context,
                 packageName, userId);
     }
+
+    /**
+     * Returns a boolean indicating whether a given package is a browser app.
+     *
+     * An app is a "browser" if it has an activity resolution that wound up
+     * marked with the 'handleAllWebDataURI' flag.
+     */
+    public static boolean isBrowserApp(Context context, String packageName, int userId) {
+        sBrowserIntent.setPackage(packageName);
+        final List<ResolveInfo> list = context.getPackageManager().queryIntentActivitiesAsUser(
+                sBrowserIntent, PackageManager.MATCH_ALL, userId);
+        for (ResolveInfo info : list) {
+            if (info.activityInfo != null && info.handleAllWebDataURI) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 9d1b3cf..95e916b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1,5 +1,7 @@
 package com.android.settingslib.bluetooth;
 
+import static com.android.settingslib.widget.AdaptiveOutlineDrawable.AdaptiveOutlineIconType.TYPE_ADVANCED;
+
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -7,6 +9,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -213,6 +216,46 @@
     }
 
     /**
+     * Build device icon with advanced outline
+     */
+    public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) {
+        final int iconSize = context.getResources().getDimensionPixelSize(
+                R.dimen.advanced_icon_size);
+        final Resources resources = context.getResources();
+
+        Bitmap bitmap = null;
+        if (drawable instanceof BitmapDrawable) {
+            bitmap = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            final int width = drawable.getIntrinsicWidth();
+            final int height = drawable.getIntrinsicHeight();
+            bitmap = createBitmap(drawable,
+                    width > 0 ? width : 1,
+                    height > 0 ? height : 1);
+        }
+
+        if (bitmap != null) {
+            final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
+                    iconSize, false);
+            bitmap.recycle();
+            return new AdaptiveOutlineDrawable(resources, resizedBitmap, TYPE_ADVANCED);
+        }
+
+        return drawable;
+    }
+
+    /**
+     * Creates a drawable with specified width and height.
+     */
+    public static Bitmap createBitmap(Drawable drawable, int width, int height) {
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /**
      * Get boolean Bluetooth metadata
      *
      * @param bluetoothDevice the BluetoothDevice to get metadata
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 40d8048..00f94f5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -21,7 +21,6 @@
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
-import android.util.Pair;
 
 import com.android.settingslib.R;
 import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -57,13 +56,11 @@
 
     @Override
     public Drawable getIcon() {
-        final Pair<Drawable, String> pair = BluetoothUtils
-                .getBtRainbowDrawableWithDescription(mContext, mCachedDevice);
-        return isFastPairDevice()
-                ? pair.first
-                : BluetoothUtils.buildBtRainbowDrawable(mContext,
-                        mContext.getDrawable(R.drawable.ic_headphone),
-                        mCachedDevice.getAddress().hashCode());
+        final Drawable drawable = getIconWithoutBackground();
+        if (!isFastPairDevice()) {
+            setColorFilter(drawable);
+        }
+        return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 8d6bc5c..ea71e52 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -55,9 +55,9 @@
 
     @Override
     public Drawable getIcon() {
-        //TODO(b/120669861): Return remote device icon uri once api is ready.
-        return BluetoothUtils.buildBtRainbowDrawable(mContext,
-                mContext.getDrawable(getDrawableResId()), getId().hashCode());
+        final Drawable drawable = getIconWithoutBackground();
+        setColorFilter(drawable);
+        return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 317077b..126f9b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -31,6 +31,9 @@
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
@@ -39,6 +42,8 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settingslib.R;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -131,6 +136,14 @@
                 getId());
     }
 
+    void setColorFilter(Drawable drawable) {
+        final ColorStateList list =
+                mContext.getResources().getColorStateList(
+                        R.color.advanced_icon_color, mContext.getTheme());
+        drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
+                PorterDuff.Mode.SRC_IN));
+    }
+
     /**
      * Get name from MediaDevice.
      *
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index c43a512..b6c0b30 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -85,8 +85,9 @@
 
     @Override
     public Drawable getIcon() {
-        return BluetoothUtils.buildBtRainbowDrawable(mContext,
-                mContext.getDrawable(getDrawableResId()), getId().hashCode());
+        final Drawable drawable = getIconWithoutBackground();
+        setColorFilter(drawable);
+        return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
     }
 
     @Override
@@ -105,7 +106,7 @@
             case TYPE_HDMI:
             case TYPE_WIRED_HEADSET:
             case TYPE_WIRED_HEADPHONES:
-                resId = com.android.internal.R.drawable.ic_bt_headphones_a2dp;
+                resId = R.drawable.ic_headphone;
                 break;
             case TYPE_BUILTIN_SPEAKER:
             default:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 421e5c0..00d1f76 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -79,13 +79,11 @@
     public void getDrawableResId_returnCorrectResId() {
         when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
 
-        assertThat(mPhoneMediaDevice.getDrawableResId())
-                .isEqualTo(com.android.internal.R.drawable.ic_bt_headphones_a2dp);
+        assertThat(mPhoneMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_headphone);
 
         when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET);
 
-        assertThat(mPhoneMediaDevice.getDrawableResId())
-                .isEqualTo(com.android.internal.R.drawable.ic_bt_headphones_a2dp);
+        assertThat(mPhoneMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_headphone);
 
         when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index fa87b62..eb7ad72 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -316,7 +316,6 @@
                     Settings.Global.KERNEL_CPU_THREAD_READER,
                     Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
                     Settings.Global.LANG_ID_UPDATE_METADATA_URL,
-                    Settings.Global.LAST_ACTIVE_USER_ID,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ae8e8e8..4e17062 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -201,6 +201,9 @@
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
+    <!-- Permission required for IncrementalLogCollectionTest -->
+    <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
+
     <!-- Permission required for storage tests - FuseDaemonHostTest -->
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
@@ -306,6 +309,9 @@
     <!-- Permission needed for CTS test - PrivilegedLocationPermissionTest -->
     <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
 
+    <!-- Permissions required for GTS test - GtsDialerAudioTestCases -->
+    <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/res/layout/dialog_bugreport_info.xml b/packages/Shell/res/layout/dialog_bugreport_info.xml
index 4bd8711..ea24279 100644
--- a/packages/Shell/res/layout/dialog_bugreport_info.xml
+++ b/packages/Shell/res/layout/dialog_bugreport_info.xml
@@ -14,58 +14,63 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:paddingTop="15dp"
-    android:paddingStart="24dp"
-    android:paddingEnd="24dp"
-    android:focusableInTouchMode="false"
-    android:focusable="false"
-    android:importantForAutofill="noExcludeDescendants"
-    android:layout_width="wrap_content"
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content">
-    <TextView
+    <LinearLayout
+        android:orientation="vertical"
+        android:paddingTop="15dp"
+        android:paddingStart="24dp"
+        android:paddingEnd="24dp"
         android:focusableInTouchMode="false"
         android:focusable="false"
-        android:inputType="textNoSuggestions"
+        android:importantForAutofill="noExcludeDescendants"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/bugreport_info_name"/>
-    <EditText
-        android:id="@+id/name"
-        android:nextFocusDown="@+id/title"
-        android:maxLength="30"
-        android:singleLine="true"
-        android:inputType="textNoSuggestions"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
-    <TextView
-        android:focusableInTouchMode="false"
-        android:focusable="false"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/bugreport_info_title"/>
-    <EditText
-        android:id="@+id/title"
-        android:nextFocusUp="@+id/name"
-        android:nextFocusDown="@+id/description"
-        android:maxLength="80"
-        android:singleLine="true"
-        android:inputType="textAutoCorrect|textCapSentences"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
-    <TextView
-        android:focusableInTouchMode="false"
-        android:focusable="false"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:editable="false"
-        android:text="@string/bugreport_info_description"/>
-    <EditText
-        android:id="@+id/description"
-        android:nextFocusUp="@+id/title"
-        android:singleLine="false"
-        android:inputType="textMultiLine|textAutoCorrect|textCapSentences"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
-</LinearLayout>
+        android:layout_height="wrap_content">
+        <TextView
+            android:focusableInTouchMode="false"
+            android:focusable="false"
+            android:inputType="textNoSuggestions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/bugreport_info_name"/>
+        <EditText
+            android:id="@+id/name"
+            android:nextFocusDown="@+id/title"
+            android:maxLength="30"
+            android:singleLine="true"
+            android:inputType="textNoSuggestions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:focusableInTouchMode="false"
+            android:focusable="false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/bugreport_info_title"/>
+        <EditText
+            android:id="@+id/title"
+            android:nextFocusUp="@+id/name"
+            android:nextFocusDown="@+id/description"
+            android:maxLength="80"
+            android:singleLine="true"
+            android:inputType="textAutoCorrect|textCapSentences"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:focusableInTouchMode="false"
+            android:focusable="false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:editable="false"
+            android:text="@string/bugreport_info_description"/>
+        <EditText
+            android:id="@+id/description"
+            android:nextFocusUp="@+id/title"
+            android:singleLine="false"
+            android:inputType="textMultiLine|textAutoCorrect|textCapSentences"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+    </LinearLayout>
+</ScrollView>
diff --git a/packages/SystemUI/res/drawable/dismiss_circle_background.xml b/packages/SystemUI/res/drawable/dismiss_circle_background.xml
index e311c52..7809c83 100644
--- a/packages/SystemUI/res/drawable/dismiss_circle_background.xml
+++ b/packages/SystemUI/res/drawable/dismiss_circle_background.xml
@@ -21,8 +21,8 @@
 
     <stroke
         android:width="1dp"
-        android:color="#66FFFFFF" />
+        android:color="#AAFFFFFF" />
 
-    <solid android:color="#B3000000" />
+    <solid android:color="#77000000" />
 
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/dismiss_target_x.xml b/packages/SystemUI/res/drawable/dismiss_target_x.xml
deleted file mode 100644
index 3672eff..0000000
--- a/packages/SystemUI/res/drawable/dismiss_target_x.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<!-- 'X' icon. -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
-        android:fillColor="#FFFFFFFF"
-        android:strokeColor="#FF000000"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml b/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml
new file mode 100644
index 0000000..8f7fb10
--- /dev/null
+++ b/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:angle="270"
+        android:startColor="#00000000"
+        android:endColor="#77000000"
+        android:type="linear" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml b/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml
new file mode 100644
index 0000000..6a0695e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@color/transparent" />
+    <item android:drawable="@drawable/floating_dismiss_gradient" />
+</transition>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_create_bubble.xml b/packages/SystemUI/res/drawable/ic_create_bubble.xml
index d58e9a3..4abbc81 100644
--- a/packages/SystemUI/res/drawable/ic_create_bubble.xml
+++ b/packages/SystemUI/res/drawable/ic_create_bubble.xml
@@ -15,11 +15,11 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="20dp"
+    android:height="20dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
       android:fillColor="#FF000000"
-      android:pathData="M22,12C22,12 22,12 22,12C22,12 22,12 22,12c0,0.56 -0.06,1.1 -0.15,1.64l-1.97,-0.33c0.15,-0.91 0.15,-1.84 -0.02,-2.75c-0.01,-0.03 -0.01,-0.07 -0.02,-0.1c-0.03,-0.18 -0.08,-0.36 -0.13,-0.54c-0.02,-0.08 -0.04,-0.16 -0.06,-0.24c-0.04,-0.14 -0.09,-0.27 -0.14,-0.41c-0.04,-0.12 -0.08,-0.24 -0.13,-0.35c-0.04,-0.09 -0.08,-0.18 -0.13,-0.27c-0.07,-0.15 -0.14,-0.3 -0.22,-0.45c-0.03,-0.05 -0.06,-0.09 -0.08,-0.14c-0.72,-1.26 -1.77,-2.31 -3.03,-3.03c-0.05,-0.03 -0.09,-0.06 -0.14,-0.08c-0.15,-0.08 -0.3,-0.15 -0.45,-0.22c-0.09,-0.04 -0.18,-0.09 -0.27,-0.13c-0.11,-0.05 -0.23,-0.09 -0.35,-0.13c-0.14,-0.05 -0.27,-0.1 -0.41,-0.14c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.18,-0.05 -0.36,-0.1 -0.54,-0.13c-0.03,-0.01 -0.07,-0.01 -0.1,-0.01c-0.95,-0.17 -1.93,-0.17 -2.88,0c-0.03,0.01 -0.07,0.01 -0.1,0.01c-0.18,0.04 -0.36,0.08 -0.54,0.13C9.85,4.3 9.77,4.32 9.69,4.34C9.55,4.38 9.42,4.44 9.28,4.49C9.17,4.53 9.05,4.57 8.93,4.61C8.84,4.65 8.75,4.7 8.66,4.74c-0.15,0.07 -0.3,0.14 -0.45,0.22C8.16,4.98 8.12,5.01 8.07,5.04C5.64,6.42 4,9.02 4,12c0,2.74 1.39,5.16 3.49,6.6c0.01,0.01 0.03,0.02 0.04,0.03c0.16,0.11 0.33,0.2 0.49,0.3c0.06,0.04 0.12,0.08 0.19,0.11c0.13,0.07 0.27,0.13 0.4,0.19c0.11,0.05 0.21,0.1 0.32,0.15c0.1,0.04 0.2,0.07 0.29,0.11c0.15,0.06 0.31,0.11 0.46,0.16c0.05,0.02 0.11,0.03 0.17,0.04c1.11,0.31 2.27,0.35 3.4,0.18l0.35,1.98c-0.54,0.09 -1.08,0.14 -1.62,0.14V22c-0.65,0 -1.28,-0.07 -1.9,-0.19c-0.01,0 -0.01,0 -0.02,0c-0.25,-0.05 -0.49,-0.11 -0.73,-0.18c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.19,-0.06 -0.37,-0.13 -0.55,-0.19c-0.13,-0.05 -0.26,-0.09 -0.39,-0.14c-0.13,-0.05 -0.25,-0.12 -0.38,-0.18c-0.18,-0.08 -0.35,-0.16 -0.53,-0.25c-0.07,-0.04 -0.14,-0.08 -0.21,-0.13c-0.22,-0.12 -0.43,-0.25 -0.64,-0.39c-0.01,-0.01 -0.02,-0.02 -0.04,-0.03c-0.51,-0.35 -1,-0.74 -1.45,-1.2l0,0C3.12,17.26 2,14.76 2,12c0,-2.76 1.12,-5.26 2.93,-7.07l0,0c0.45,-0.45 0.93,-0.84 1.44,-1.19C6.39,3.73 6.4,3.72 6.42,3.71c0.2,-0.14 0.41,-0.26 0.62,-0.38c0.08,-0.05 0.15,-0.09 0.23,-0.14c0.17,-0.09 0.33,-0.16 0.5,-0.24c0.13,-0.06 0.27,-0.13 0.4,-0.19C8.3,2.71 8.42,2.67 8.55,2.63c0.19,-0.07 0.38,-0.14 0.58,-0.2c0.07,-0.02 0.14,-0.03 0.21,-0.05C10.18,2.14 11.07,2 12,2c0.65,0 1.29,0.07 1.91,0.19c0,0 0,0 0,0c0.25,0.05 0.5,0.11 0.75,0.18c0.07,0.02 0.14,0.03 0.22,0.06c0.19,0.06 0.38,0.13 0.57,0.2c0.12,0.05 0.25,0.09 0.37,0.14c0.14,0.06 0.27,0.12 0.4,0.18c0.17,0.08 0.34,0.16 0.51,0.25c0.08,0.04 0.15,0.09 0.23,0.14c0.21,0.12 0.42,0.24 0.62,0.38c0.01,0.01 0.03,0.02 0.04,0.03c0.51,0.35 0.99,0.74 1.45,1.19c0.24,0.24 0.47,0.49 0.68,0.75c0.04,0.04 0.06,0.09 0.1,0.13c0.17,0.22 0.34,0.45 0.5,0.68c0.01,0.01 0.02,0.03 0.03,0.04c0.69,1.05 1.17,2.21 1.42,3.44c0,0 0,0.01 0,0.01c0.06,0.29 0.1,0.58 0.13,0.87c0.01,0.04 0.01,0.09 0.02,0.13C21.98,11.32 22,11.66 22,12zM18.5,15c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S20.43,15 18.5,15z"/>
-</vector>
\ No newline at end of file
+      android:pathData="M23,5v8h-2V5H3v14h10v2v0H3c-1.1,0 -2,-0.9 -2,-2V5c0,-1.1 0.9,-2 2,-2h18C22.1,3 23,3.9 23,5zM10,8v2.59L5.71,6.29L4.29,7.71L8.59,12H6v2h6V8H10zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_music_note.xml b/packages/SystemUI/res/drawable/ic_music_note.xml
new file mode 100644
index 0000000..30959a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_music_note.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_stop_bubble.xml b/packages/SystemUI/res/drawable/ic_stop_bubble.xml
index 11bc741..6cf67a7 100644
--- a/packages/SystemUI/res/drawable/ic_stop_bubble.xml
+++ b/packages/SystemUI/res/drawable/ic_stop_bubble.xml
@@ -15,11 +15,11 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="20dp"
+    android:height="20dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
       android:fillColor="#FF000000"
-      android:pathData="M21.98,18.32l-3.3,-3.3C20.47,15.11 21.89,16.53 21.98,18.32zM8.66,4.74C8.75,4.7 8.84,4.65 8.93,4.61c0.11,-0.05 0.23,-0.09 0.35,-0.13c0.14,-0.05 0.27,-0.1 0.41,-0.14C9.77,4.32 9.85,4.3 9.92,4.28c0.18,-0.05 0.36,-0.1 0.54,-0.13c0.03,-0.01 0.07,-0.01 0.1,-0.01c0.95,-0.17 1.93,-0.17 2.88,0c0.03,0.01 0.07,0.01 0.1,0.01c0.18,0.04 0.36,0.08 0.54,0.13c0.08,0.02 0.16,0.04 0.23,0.06c0.14,0.04 0.27,0.09 0.41,0.14c0.12,0.04 0.23,0.08 0.35,0.13c0.09,0.04 0.18,0.09 0.27,0.13c0.15,0.07 0.3,0.14 0.45,0.22c0.05,0.03 0.09,0.06 0.14,0.08c1.26,0.72 2.31,1.77 3.03,3.03c0.03,0.05 0.06,0.09 0.08,0.14c0.08,0.15 0.15,0.3 0.22,0.45c0.04,0.09 0.09,0.18 0.13,0.27c0.05,0.11 0.09,0.23 0.13,0.35c0.05,0.13 0.1,0.27 0.14,0.41c0.02,0.08 0.04,0.16 0.06,0.24c0.05,0.18 0.1,0.36 0.13,0.54c0.01,0.03 0.01,0.07 0.02,0.1c0.16,0.91 0.17,1.84 0.02,2.75l1.97,0.33C21.94,13.1 22,12.56 22,12c0,0 0,0 0,0s0,0 0,0c0,-0.34 -0.02,-0.68 -0.05,-1.01c0,-0.04 -0.01,-0.09 -0.02,-0.13c-0.03,-0.29 -0.07,-0.58 -0.13,-0.87c0,0 0,-0.01 0,-0.01c-0.25,-1.23 -0.73,-2.39 -1.42,-3.44c-0.01,-0.01 -0.02,-0.03 -0.03,-0.04c-0.15,-0.23 -0.32,-0.46 -0.5,-0.68c-0.03,-0.04 -0.06,-0.09 -0.1,-0.13c-0.21,-0.26 -0.44,-0.51 -0.68,-0.75c-0.45,-0.45 -0.94,-0.84 -1.45,-1.19c-0.01,-0.01 -0.03,-0.02 -0.04,-0.03c-0.2,-0.14 -0.41,-0.26 -0.62,-0.38c-0.08,-0.04 -0.15,-0.09 -0.23,-0.14c-0.17,-0.09 -0.34,-0.17 -0.51,-0.25c-0.13,-0.06 -0.26,-0.13 -0.4,-0.18c-0.12,-0.05 -0.25,-0.09 -0.37,-0.14c-0.19,-0.07 -0.38,-0.14 -0.57,-0.2c-0.07,-0.02 -0.14,-0.04 -0.22,-0.06c-0.25,-0.07 -0.5,-0.13 -0.75,-0.18c0,0 0,0 0,0C13.29,2.07 12.65,2 12,2c-0.93,0 -1.82,0.14 -2.67,0.37C9.26,2.39 9.19,2.41 9.12,2.43c-0.2,0.06 -0.39,0.13 -0.58,0.2C8.42,2.67 8.3,2.71 8.18,2.76c-0.14,0.06 -0.27,0.12 -0.4,0.19C7.61,3.03 7.44,3.1 7.27,3.19C7.19,3.24 7.12,3.29 7.04,3.33C7.03,3.34 7.02,3.34 7.01,3.35l1.48,1.48C8.55,4.8 8.6,4.77 8.66,4.74zM2.71,1.29L1.29,2.71l2.97,2.97C2.85,7.4 2,9.6 2,12c0,2.76 1.12,5.26 2.93,7.07l0,0c0.45,0.45 0.94,0.85 1.45,1.2c0.01,0.01 0.02,0.02 0.04,0.03c0.21,0.14 0.42,0.27 0.64,0.39c0.07,0.04 0.14,0.09 0.21,0.13c0.17,0.09 0.35,0.17 0.53,0.25c0.13,0.06 0.25,0.12 0.38,0.18c0.13,0.05 0.26,0.1 0.39,0.14c0.18,0.07 0.36,0.14 0.55,0.19c0.08,0.02 0.16,0.04 0.23,0.06c0.24,0.07 0.48,0.13 0.73,0.18c0.01,0 0.01,0 0.02,0C10.72,21.93 11.35,22 12,22v-0.01c0.54,0 1.08,-0.05 1.62,-0.14l-0.35,-1.98c-1.13,0.18 -2.29,0.13 -3.4,-0.18c-0.06,-0.02 -0.11,-0.03 -0.17,-0.04c-0.16,-0.05 -0.31,-0.11 -0.46,-0.16c-0.1,-0.04 -0.2,-0.07 -0.29,-0.11c-0.11,-0.05 -0.22,-0.1 -0.32,-0.15c-0.13,-0.06 -0.27,-0.12 -0.4,-0.19c-0.06,-0.03 -0.13,-0.08 -0.19,-0.11c-0.17,-0.1 -0.33,-0.19 -0.49,-0.3c-0.01,-0.01 -0.03,-0.02 -0.04,-0.03C5.39,17.16 4,14.74 4,12c0,-1.85 0.64,-3.54 1.7,-4.89l9.73,9.73C15.16,17.33 15,17.9 15,18.5c0,1.93 1.57,3.5 3.5,3.5c0.6,0 1.17,-0.16 1.66,-0.43l1.13,1.13l1.41,-1.41L2.71,1.29z"/>
+      android:pathData="M11.29,14.71L7,10.41V13H5V7h6v2H8.41l4.29,4.29L11.29,14.71zM21,3H3C1.9,3 1,3.9 1,5v14c0,1.1 0.9,2 2,2h10v0v-2H3V5h18v8h2V5C23,3.9 22.1,3 21,3zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/>
 </vector>
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index b348222..803b0c6 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -52,39 +52,20 @@
             android:gravity="center_vertical"
             android:layout_alignEnd="@id/conversation_icon"
             android:layout_toEndOf="@id/conversation_icon">
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:gravity="start"
-                android:orientation="horizontal">
-                <TextView
-                    android:id="@+id/name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    style="@style/TextAppearance.NotificationImportanceChannel"/>
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_centerVertical="true"
-                    style="@style/TextAppearance.NotificationImportanceHeader"
-                    android:layout_marginStart="2dp"
-                    android:layout_marginEnd="2dp"
-                    android:text="@*android:string/notification_header_divider_symbol" />
-                <TextView
-                    android:id="@+id/parent_channel_name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    style="@style/TextAppearance.NotificationImportanceChannel"/>
-
-            </LinearLayout>
             <TextView
-                android:id="@+id/pkg_name"
+                android:id="@+id/name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                style="@style/TextAppearance.NotificationImportanceChannelGroup"
                 android:ellipsize="end"
                 android:textDirection="locale"
-                android:maxLines="1"/>
+                style="@style/TextAppearance.NotificationImportanceChannel"/>
+            <TextView
+                android:id="@+id/parent_channel_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:textDirection="locale"
+                style="@style/TextAppearance.NotificationImportanceChannel"/>
             <TextView
                 android:id="@+id/group_name"
                 android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
index 846c538..15f398a 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -23,7 +23,7 @@
     android:paddingStart="@dimen/qs_footer_padding_start"
     android:paddingEnd="@dimen/qs_footer_padding_end"
     android:gravity="center_vertical"
-    android:background="?android:attr/colorPrimary" >
+    android:background="@android:color/transparent">
 
     <TextView
         android:id="@+id/footer_text"
@@ -32,7 +32,7 @@
         android:gravity="start"
         android:layout_weight="1"
         android:textAppearance="@style/TextAppearance.QS.TileLabel"
-        android:textColor="?android:attr/textColorSecondary"/>
+        style="@style/qs_security_footer"/>
 
     <ImageView
         android:id="@+id/footer_icon"
@@ -40,6 +40,6 @@
         android:layout_height="@dimen/qs_footer_icon_size"
         android:contentDescription="@null"
         android:src="@drawable/ic_info_outline"
-        android:tint="?android:attr/textColorSecondary"/>
+        style="@style/qs_security_footer"/>
 
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
index af6f9bb..0c4d5a2 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
@@ -16,21 +16,20 @@
   -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
+    android:layout_width="250dp"
     android:layout_height="48dp"
     android:orientation="vertical"
-    android:padding="10dp"
-    android:layout_weight="1">
+    android:padding="13dp">
     <TextView
         android:id="@+id/screen_recording_dialog_source_text"
-        android:layout_width="match_parent"
+        android:layout_width="250dp"
         android:layout_height="match_parent"
         android:layout_gravity="center_vertical"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textColor="?android:attr/textColorPrimary"/>
     <TextView
         android:id="@+id/screen_recording_dialog_source_description"
-        android:layout_width="wrap_content"
+        android:layout_width="250dp"
         android:layout_height="wrap_content"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textColor="?android:attr/textColorSecondary"/>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 4fdeb6f..50261e1 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -29,4 +29,9 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
+    <style name="qs_security_footer" parent="@style/qs_theme">
+        <item name="android:textColor">#B3FFFFFF</item> <!-- 70% white -->
+        <item name="android:tint">#FFFFFFFF</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/res/values-sw320dp/dimens.xml b/packages/SystemUI/res/values-sw320dp/dimens.xml
deleted file mode 100644
index c110113..0000000
--- a/packages/SystemUI/res/values-sw320dp/dimens.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<resources>
-    <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
-    <dimen name="global_actions_grid_horizontal_padding">3dp</dimen>
-
-    <dimen name="global_actions_grid_item_side_margin">5dp</dimen>
-    <dimen name="global_actions_grid_item_vertical_margin">4dp</dimen>
-    <dimen name="global_actions_grid_item_width">64dp</dimen>
-    <dimen name="global_actions_grid_item_height">64dp</dimen>
-
-    <dimen name="global_actions_grid_item_icon_width">20dp</dimen>
-    <dimen name="global_actions_grid_item_icon_height">20dp</dimen>
-    <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen>
-    <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen>
-    <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
-
-    <!-- Home Controls -->
-    <dimen name="global_actions_side_margin">10dp</dimen>
-</resources>
-
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d32ac6a..73d8e9a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -974,7 +974,9 @@
     <dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen>
 
     <!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
-    <dimen name="floating_dismiss_gradient_height">176dp</dimen>
+    <dimen name="floating_dismiss_gradient_height">250dp</dimen>
+
+    <dimen name="floating_dismiss_bottom_margin">50dp</dimen>
 
     <!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. -->
     <dimen name="pip_dismiss_text_bottom_margin">24dp</dimen>
@@ -1032,8 +1034,23 @@
     <dimen name="global_actions_grid_container_shadow_offset">20dp</dimen>
     <dimen name="global_actions_grid_container_negative_shadow_offset">-20dp</dimen>
 
+    <!-- Global actions grid -->
+    <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
+    <dimen name="global_actions_grid_horizontal_padding">3dp</dimen>
+
+    <dimen name="global_actions_grid_item_side_margin">5dp</dimen>
+    <dimen name="global_actions_grid_item_vertical_margin">4dp</dimen>
+    <dimen name="global_actions_grid_item_width">64dp</dimen>
+    <dimen name="global_actions_grid_item_height">64dp</dimen>
+
+    <dimen name="global_actions_grid_item_icon_width">20dp</dimen>
+    <dimen name="global_actions_grid_item_icon_height">20dp</dimen>
+    <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen>
+    <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen>
+    <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
+
     <!-- Margins at the left and right of the power menu and home controls widgets. -->
-    <dimen name="global_actions_side_margin">16dp</dimen>
+    <dimen name="global_actions_side_margin">10dp</dimen>
 
     <!-- Amount to shift the layout when exiting/entering for controls activities -->
     <dimen name="global_actions_controls_y_translation">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 39237ac..0314fc8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2781,6 +2781,8 @@
 
     <!-- Close the controls associated with a specific media session [CHAR_LIMIT=NONE] -->
     <string name="controls_media_close_session">Close this media session</string>
+    <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_resume">Resume</string>
 
     <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
     <string name="controls_error_timeout">Inactive, check app</string>
@@ -2788,7 +2790,13 @@
          a retry will be attempted [CHAR LIMIT=30] -->
     <string name="controls_error_retryable">Error, retrying\u2026</string>
     <!-- Error message indicating that the control is no longer available in the application [CHAR LIMIT=30] -->
-    <string name="controls_error_removed">Device removed</string>
+    <string name="controls_error_removed">Not found</string>
+    <!-- Title for dialog indicating that the control is no longer available in the application [CHAR LIMIT=30] -->
+    <string name="controls_error_removed_title">Control is unavailable</string>
+    <!-- Message body for dialog indicating that the control is no longer available in the application [CHAR LIMIT=NONE] -->
+    <string name="controls_error_removed_message">Couldn\u2019t access <xliff:g id="device" example="Backdoor lock">%1$s</xliff:g>. Check the <xliff:g id="application" example="Google Home">%2$s</xliff:g> app to make sure the control is still available and that the app settings haven\u2019t changed.</string>
+    <!-- Text for button to open the corresponding application [CHAR_LIMIT=20] -->
+    <string name="controls_open_app">Open app</string>
     <!-- Error message indicating that an unspecified error occurred while getting the status [CHAR LIMIT=30] -->
     <string name="controls_error_generic">Can\u2019t load status</string>
     <!-- Error message indicating that a control action failed [CHAR_LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ed36bdb..39f78bf 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -387,6 +387,11 @@
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
     </style>
 
+    <style name="qs_security_footer" parent="@style/qs_theme">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:tint">?android:attr/textColorSecondary</item>
+    </style>
+
     <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:colorAccent">@color/remote_input_accent</item>
     </style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 35ad422..655008b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -23,10 +23,11 @@
 import android.view.MotionEvent;
 
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * Temporary callbacks into SystemUI.
- * Next id = 26
+ * Next id = 27
  */
 interface ISystemUiProxy {
 
@@ -122,6 +123,9 @@
 
     /**
      * Handle the provided image as if it was a screenshot.
+     *
+     * Deprecated, use handleImageBundleAsScreenshot with image bundle and UserTask
+     * @deprecated
      */
     void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen,
               in Insets visibleInsets, int taskId) = 21;
@@ -146,4 +150,10 @@
      * @param rotation indicates which Surface.Rotation the gesture was started in
      */
     void onQuickSwitchToNewTask(int rotation) = 25;
+
+    /**
+     * Handle the provided image as if it was a screenshot.
+     */
+    void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
+              in Insets visibleInsets, in Task.TaskKey task) = 26;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl
new file mode 100644
index 0000000..e7cad2a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.model;
+
+parcelable Task.TaskKey;
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index dcb134e..186379a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -26,6 +26,8 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.view.ViewDebug;
 
 import com.android.systemui.shared.recents.utilities.Utilities;
@@ -52,8 +54,10 @@
         void onTaskWindowingModeChanged();
     }
 
-    /* The Task Key represents the unique primary key for the task */
-    public static class TaskKey {
+    /**
+     * The Task Key represents the unique primary key for the task
+     */
+    public static class TaskKey implements Parcelable {
         @ViewDebug.ExportedProperty(category="recents")
         public final int id;
         @ViewDebug.ExportedProperty(category="recents")
@@ -157,6 +161,48 @@
         private void updateHashCode() {
             mHashCode = Objects.hash(id, windowingMode, userId);
         }
+
+        public static final Parcelable.Creator<TaskKey> CREATOR =
+                new Parcelable.Creator<TaskKey>() {
+                    @Override
+                    public TaskKey createFromParcel(Parcel source) {
+                        return TaskKey.readFromParcel(source);
+                    }
+
+                    @Override
+                    public TaskKey[] newArray(int size) {
+                        return new TaskKey[size];
+                    }
+                };
+
+        @Override
+        public final void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeInt(id);
+            parcel.writeInt(windowingMode);
+            parcel.writeTypedObject(baseIntent, flags);
+            parcel.writeInt(userId);
+            parcel.writeLong(lastActiveTime);
+            parcel.writeInt(displayId);
+            parcel.writeTypedObject(sourceComponent, flags);
+        }
+
+        private static TaskKey readFromParcel(Parcel parcel) {
+            int id = parcel.readInt();
+            int windowingMode = parcel.readInt();
+            Intent baseIntent = parcel.readTypedObject(Intent.CREATOR);
+            int userId = parcel.readInt();
+            long lastActiveTime = parcel.readLong();
+            int displayId = parcel.readInt();
+            ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR);
+
+            return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId,
+                    lastActiveTime, displayId);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
     }
 
     @ViewDebug.ExportedProperty(deepExport=true, prefix="key_")
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java
new file mode 100644
index 0000000..b79fcbd
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.utilities;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+/**
+ * Utils for working with Bitmaps.
+ */
+public final class BitmapUtil {
+    private static final String KEY_BUFFER = "bitmap_util_buffer";
+    private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
+
+    private BitmapUtil(){ }
+
+    /**
+     * Creates a Bundle that represents the given Bitmap.
+     * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
+     * copies when passing across processes, only pass to processes you trust.
+     *
+     * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
+     * returned Bundle should be treated as a standalone object.
+     *
+     * @param bitmap to convert to bundle
+     * @return a Bundle representing the bitmap, should only be parsed by
+     *         {@link #bundleToHardwareBitmap(Bundle)}
+     */
+    public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
+        if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+            throw new IllegalArgumentException(
+                    "Passed bitmap must have hardware config, found: " + bitmap.getConfig());
+        }
+
+        // Bitmap assumes SRGB for null color space
+        ParcelableColorSpace colorSpace =
+                bitmap.getColorSpace() == null
+                        ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
+                        : new ParcelableColorSpace(bitmap.getColorSpace());
+
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
+        bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
+
+        return bundle;
+    }
+
+    /**
+     * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
+     *
+     * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing this
+     * Bitmap on to any other source.
+     *
+     * @param bundle containing the bitmap
+     * @return a hardware Bitmap
+     */
+    public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
+        if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
+            throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
+        }
+
+        HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER);
+        ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE);
+
+        return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer), colorSpace);
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index dd5cc7c..796aaee 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -114,4 +114,7 @@
 
     /** @see ITaskStackListener#onRecentTaskListFrozenChanged(boolean) */
     public void onRecentTaskListFrozenChanged(boolean frozen) { }
+
+    /** @see ITaskStackListener#onActivityRotation()*/
+    public void onActivityRotation() { }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index a76a901..13f7993 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -237,6 +237,11 @@
         mHandler.obtainMessage(H.ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget();
     }
 
+    @Override
+    public void onActivityRotation() {
+        mHandler.obtainMessage(H.ON_ACTIVITY_ROTATION).sendToTarget();
+    }
+
     private final class H extends Handler {
         private static final int ON_TASK_STACK_CHANGED = 1;
         private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
@@ -260,6 +265,7 @@
         private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 22;
         private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 23;
         private static final int ON_TASK_DESCRIPTION_CHANGED = 24;
+        private static final int ON_ACTIVITY_ROTATION = 25;
 
 
         public H(Looper looper) {
@@ -427,6 +433,12 @@
                         }
                         break;
                     }
+                    case ON_ACTIVITY_ROTATION: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityRotation();
+                        }
+                        break;
+                    }
                 }
             }
             if (msg.obj instanceof SomeArgs) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java
index dd61326..73783ae 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.shared.system;
 
+import android.graphics.HardwareRenderer;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
@@ -50,7 +51,13 @@
 
     public void registerRtFrameCallback(LongConsumer callback) {
         if (mViewRoot != null) {
-            mViewRoot.registerRtFrameCallback(callback::accept);
+            mViewRoot.registerRtFrameCallback(
+                    new HardwareRenderer.FrameDrawingCallback() {
+                        @Override
+                        public void onFrameDraw(long l) {
+                            callback.accept(l);
+                        }
+                    });
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 15eda06..97a7304 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -21,6 +21,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
+import android.annotation.DimenRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
@@ -104,27 +105,39 @@
     private Path mDotPath;
     private int mFlags;
 
+    @NonNull
+    private UserHandle mUser;
+    @NonNull
+    private String mPackageName;
+    private int mDesiredHeight;
+    @DimenRes
+    private int mDesiredHeightResId;
+
     /**
      * Create a bubble with limited information based on given {@link ShortcutInfo}.
      * Note: Currently this is only being used when the bubble is persisted to disk.
      */
-    Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo) {
+    Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
+            final int desiredHeight, final int desiredHeightResId) {
         Objects.requireNonNull(key);
         Objects.requireNonNull(shortcutInfo);
         mShortcutInfo = shortcutInfo;
         mKey = key;
         mFlags = 0;
+        mUser = shortcutInfo.getUserHandle();
+        mPackageName = shortcutInfo.getPackage();
+        mDesiredHeight = desiredHeight;
+        mDesiredHeightResId = desiredHeightResId;
     }
 
     /** Used in tests when no UI is required. */
     @VisibleForTesting(visibility = PRIVATE)
-    Bubble(NotificationEntry e,
-            BubbleController.NotificationSuppressionChangedListener listener) {
-        mEntry = e;
+    Bubble(@NonNull final NotificationEntry e,
+            @Nullable final BubbleController.NotificationSuppressionChangedListener listener) {
+        Objects.requireNonNull(e);
         mKey = e.getKey();
-        mLastUpdated = e.getSbn().getPostTime();
         mSuppressionListener = listener;
-        mFlags = e.getSbn().getNotification().flags;
+        setEntry(e);
     }
 
     @Override
@@ -137,17 +150,14 @@
         return mEntry;
     }
 
-    @Nullable
+    @NonNull
     public UserHandle getUser() {
-        if (mEntry != null) return mEntry.getSbn().getUser();
-        if (mShortcutInfo != null) return mShortcutInfo.getUserHandle();
-        return null;
+        return mUser;
     }
 
+    @NonNull
     public String getPackageName() {
-        return mEntry == null
-                ? mShortcutInfo == null ? null : mShortcutInfo.getPackage()
-                : mEntry.getSbn().getPackageName();
+        return mPackageName;
     }
 
     @Override
@@ -318,9 +328,18 @@
     /**
      * Sets the entry associated with this bubble.
      */
-    void setEntry(NotificationEntry entry) {
+    void setEntry(@NonNull final NotificationEntry entry) {
+        Objects.requireNonNull(entry);
+        Objects.requireNonNull(entry.getSbn());
         mEntry = entry;
         mLastUpdated = entry.getSbn().getPostTime();
+        mFlags = entry.getSbn().getNotification().flags;
+        mPackageName = entry.getSbn().getPackageName();
+        mUser = entry.getSbn().getUser();
+        if (entry.getBubbleMetadata() != null) {
+            mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
+            mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
+        }
     }
 
     /**
@@ -434,28 +453,30 @@
         return mFlyoutMessage;
     }
 
+    int getRawDesiredHeight() {
+        return mDesiredHeight;
+    }
+
+    int getRawDesiredHeightResId() {
+        return mDesiredHeightResId;
+    }
+
     float getDesiredHeight(Context context) {
-        if (mEntry == null) return 0;
-        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
-        boolean useRes = data.getDesiredHeightResId() != 0;
+        boolean useRes = mDesiredHeightResId != 0;
         if (useRes) {
-            return getDimenForPackageUser(context, data.getDesiredHeightResId(),
-                    mEntry.getSbn().getPackageName(),
-                    mEntry.getSbn().getUser().getIdentifier());
+            return getDimenForPackageUser(context, mDesiredHeightResId, mPackageName,
+                    mUser.getIdentifier());
         } else {
-            return data.getDesiredHeight()
-                    * context.getResources().getDisplayMetrics().density;
+            return mDesiredHeight * context.getResources().getDisplayMetrics().density;
         }
     }
 
     String getDesiredHeightString() {
-        if (mEntry == null) return String.valueOf(0);
-        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
-        boolean useRes = data.getDesiredHeightResId() != 0;
+        boolean useRes = mDesiredHeightResId != 0;
         if (useRes) {
-            return String.valueOf(data.getDesiredHeightResId());
+            return String.valueOf(mDesiredHeightResId);
         } else {
-            return String.valueOf(data.getDesiredHeight());
+            return String.valueOf(mDesiredHeight);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index c2b9195..d20f405 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -77,7 +77,8 @@
             var shortcutId = b.shortcutInfo?.id
             if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId
             if (shortcutId == null) return@mapNotNull null
-            BubbleEntity(userId, b.packageName, shortcutId, b.key)
+            BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight,
+                    b.rawDesiredHeightResId)
         }
     }
 
@@ -158,7 +159,8 @@
         val bubbles = entities.mapNotNull { entity ->
             shortcutMap[ShortcutKey(entity.userId, entity.packageName)]
                     ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
-                    ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo) }
+                    ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight,
+                            entity.desiredHeightResId) }
         }
         uiScope.launch { cb(bubbles) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index dfa71ba..8a80c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -47,6 +47,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.graphics.drawable.TransitionDrawable;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.Log;
@@ -133,6 +134,9 @@
     /** Percent to darken the bubbles when they're in the dismiss target. */
     private static final float DARKEN_PERCENT = 0.3f;
 
+    /** Duration of the dismiss scrim fading in/out. */
+    private static final int DISMISS_TRANSITION_DURATION_MS = 200;
+
     /** How long to wait, in milliseconds, before hiding the flyout. */
     @VisibleForTesting
     static final int FLYOUT_HIDE_AFTER = 5000;
@@ -752,7 +756,7 @@
         final View targetView = new DismissCircleView(context);
         final FrameLayout.LayoutParams newParams =
                 new FrameLayout.LayoutParams(targetSize, targetSize);
-        newParams.gravity = Gravity.CENTER;
+        newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
         targetView.setLayoutParams(newParams);
         mDismissTargetAnimator = PhysicsAnimator.getInstance(targetView);
 
@@ -761,9 +765,16 @@
                 MATCH_PARENT,
                 getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
                 Gravity.BOTTOM));
+
+        final int bottomMargin =
+                getResources().getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin);
+        mDismissTargetContainer.setPadding(0, 0, 0, bottomMargin);
+        mDismissTargetContainer.setClipToPadding(false);
         mDismissTargetContainer.setClipChildren(false);
         mDismissTargetContainer.addView(targetView);
         mDismissTargetContainer.setVisibility(View.INVISIBLE);
+        mDismissTargetContainer.setBackgroundResource(
+                R.drawable.floating_dismiss_gradient_transition);
         addView(mDismissTargetContainer);
 
         // Start translated down so the target springs up.
@@ -1884,6 +1895,9 @@
         mDismissTargetContainer.setZ(Short.MAX_VALUE - 1);
         mDismissTargetContainer.setVisibility(VISIBLE);
 
+        ((TransitionDrawable) mDismissTargetContainer.getBackground()).startTransition(
+                DISMISS_TRANSITION_DURATION_MS);
+
         mDismissTargetAnimator.cancel();
         mDismissTargetAnimator
                 .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring)
@@ -1901,6 +1915,9 @@
 
         mShowingDismiss = false;
 
+        ((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition(
+                DISMISS_TRANSITION_DURATION_MS);
+
         mDismissTargetAnimator
                 .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(),
                         mDismissTargetSpring)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
index 4348261..355c4b1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
@@ -15,11 +15,14 @@
  */
 package com.android.systemui.bubbles.storage
 
+import android.annotation.DimenRes
 import android.annotation.UserIdInt
 
 data class BubbleEntity(
     @UserIdInt val userId: Int,
     val packageName: String,
     val shortcutId: String,
-    val key: String
+    val key: String,
+    val desiredHeight: Int,
+    @DimenRes val desiredHeightResId: Int
 )
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
index 1df9f72..a8faf25 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
@@ -31,6 +31,8 @@
 private const val ATTR_PACKAGE = "pkg"
 private const val ATTR_SHORTCUT_ID = "sid"
 private const val ATTR_KEY = "key"
+private const val ATTR_DESIRED_HEIGHT = "h"
+private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid"
 
 /**
  * Writes the bubbles in xml format into given output stream.
@@ -59,6 +61,8 @@
         serializer.attribute(null, ATTR_PACKAGE, bubble.packageName)
         serializer.attribute(null, ATTR_SHORTCUT_ID, bubble.shortcutId)
         serializer.attribute(null, ATTR_KEY, bubble.key)
+        serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString())
+        serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString())
         serializer.endTag(null, TAG_BUBBLE)
     } catch (e: IOException) {
         throw RuntimeException(e)
@@ -86,7 +90,9 @@
             parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null,
             parser.getAttributeWithName(ATTR_PACKAGE) ?: return null,
             parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null,
-            parser.getAttributeWithName(ATTR_KEY) ?: return null
+            parser.getAttributeWithName(ATTR_KEY) ?: return null,
+            parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null,
+            parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index f07f316..994557c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -118,6 +118,7 @@
     var behavior: Behavior? = null
     var lastAction: ControlAction? = null
     var isLoading = false
+    var visibleDialog: Dialog? = null
     private var lastChallengeDialog: Dialog? = null
     private val onDialogCancel: () -> Unit = { lastChallengeDialog = null }
 
@@ -197,18 +198,24 @@
     fun dismiss() {
         lastChallengeDialog?.dismiss()
         lastChallengeDialog = null
+        visibleDialog?.dismiss()
+        visibleDialog = null
     }
 
     fun setTransientStatus(tempStatus: String) {
         val previousText = status.getText()
 
         cancelUpdate = uiExecutor.executeDelayed({
-            setStatusText(previousText)
-            updateContentDescription()
+            animateStatusChange(/* animated */ true, {
+                setStatusText(previousText, /* immediately */ true)
+                updateContentDescription()
+            })
         }, UPDATE_DELAY_IN_MILLIS)
 
-        setStatusText(tempStatus)
-        updateContentDescription()
+        animateStatusChange(/* animated */ true, {
+            setStatusText(tempStatus, /* immediately */ true)
+            updateContentDescription()
+        })
     }
 
     private fun updateContentDescription() =
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 35ebac5..d31b6eb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -129,7 +129,10 @@
                         SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName)
                     }
                     uiExecutor.execute {
-                        onResult(lastItems)
+                        parent.removeAllViews()
+                        if (lastItems.size > 0) {
+                            onResult(lastItems)
+                        }
                     }
                 }
             }
@@ -189,8 +192,6 @@
     }
 
     private fun showSeedingView(items: List<SelectionItem>) {
-        parent.removeAllViews()
-
         val inflater = LayoutInflater.from(context)
         inflater.inflate(R.layout.controls_no_favorites, parent, true)
         val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle)
@@ -198,8 +199,6 @@
     }
 
     private fun showInitialSetupView(items: List<SelectionItem>) {
-        parent.removeAllViews()
-
         val inflater = LayoutInflater.from(context)
         inflater.inflate(R.layout.controls_no_favorites, parent, true)
 
@@ -263,7 +262,6 @@
     }
 
     private fun showControlsView(items: List<SelectionItem>) {
-        parent.removeAllViews()
         controlViewsById.clear()
 
         createListView()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index f970152..9ec1452 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -64,6 +64,10 @@
         }
 
         override fun onActivityViewDestroyed(view: ActivityView) {}
+
+        override fun onTaskRemovalStarted(taskId: Int) {
+            dismiss()
+        }
     }
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index bf3835d..6bf1897 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -16,7 +16,13 @@
 
 package com.android.systemui.controls.ui
 
+import android.app.AlertDialog
+import android.app.PendingIntent
+import android.content.DialogInterface
+import android.content.pm.PackageManager
 import android.service.controls.Control
+import android.view.View
+import android.view.WindowManager
 
 import com.android.systemui.R
 
@@ -31,7 +37,17 @@
         val status = cws.control?.status ?: Control.STATUS_UNKNOWN
         val msg = when (status) {
             Control.STATUS_ERROR -> R.string.controls_error_generic
-            Control.STATUS_NOT_FOUND -> R.string.controls_error_removed
+            Control.STATUS_DISABLED -> R.string.controls_error_timeout
+            Control.STATUS_NOT_FOUND -> {
+                cvh.layout.setOnClickListener(View.OnClickListener() {
+                    showNotFoundDialog(cvh, cws)
+                })
+                cvh.layout.setOnLongClickListener(View.OnLongClickListener() {
+                    showNotFoundDialog(cvh, cws)
+                    true
+                })
+                R.string.controls_error_removed
+            }
             else -> {
                 cvh.isLoading = true
                 com.android.internal.R.string.loading
@@ -40,4 +56,42 @@
         cvh.setStatusText(cvh.context.getString(msg))
         cvh.applyRenderInfo(false, colorOffset)
     }
+
+    private fun showNotFoundDialog(cvh: ControlViewHolder, cws: ControlWithState) {
+        val pm = cvh.context.getPackageManager()
+        val ai = pm.getApplicationInfo(cws.componentName.packageName, PackageManager.GET_META_DATA)
+        val appLabel = pm.getApplicationLabel(ai)
+        val builder = AlertDialog.Builder(
+            cvh.context,
+            android.R.style.Theme_DeviceDefault_Dialog_Alert
+        ).apply {
+            val res = cvh.context.resources
+            setTitle(res.getString(R.string.controls_error_removed_title))
+            setMessage(res.getString(
+                R.string.controls_error_removed_message, cvh.title.getText(), appLabel))
+            setPositiveButton(
+                R.string.controls_open_app,
+                DialogInterface.OnClickListener { dialog, _ ->
+                    try {
+                        cws.control?.getAppIntent()?.send()
+                    } catch (e: PendingIntent.CanceledException) {
+                        cvh.setTransientStatus(
+                            cvh.context.resources.getString(R.string.controls_error_failed))
+                    }
+                    dialog.dismiss()
+            })
+            setNegativeButton(
+                android.R.string.cancel,
+                DialogInterface.OnClickListener { dialog, _ ->
+                    dialog.cancel()
+                }
+            )
+        }
+        cvh.visibleDialog = builder.create().apply {
+            getWindow().apply {
+                setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+                show()
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 0ec4cc5..4003f41 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -268,7 +268,7 @@
 
     private fun format(primaryFormat: String, backupFormat: String, value: Float): String {
         return try {
-            String.format(primaryFormat, value)
+            String.format(primaryFormat, findNearestStep(value))
         } catch (e: IllegalFormatException) {
             Log.w(ControlsUiController.TAG, "Illegal format in range template", e)
             if (backupFormat == "") {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 95a9006..951dc99 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -71,7 +71,7 @@
             DockManager dockManager, @Nullable IWallpaperManager wallpaperManager,
             ProximitySensor proximitySensor,
             DelayedWakeLock.Builder delayedWakeLockBuilder, @Main Handler handler,
-            DelayableExecutor delayableExecutor,
+            @Main DelayableExecutor delayableExecutor,
             BiometricUnlockController biometricUnlockController,
             BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) {
         mFalsingManager = falsingManager;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 71ce36c..7e009b4 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -391,7 +391,15 @@
 
         if (controlsComponent.getControlsListingController().isPresent()) {
             controlsComponent.getControlsListingController().get()
-                    .addCallback(list -> mControlsServiceInfos = list);
+                    .addCallback(list -> {
+                        mControlsServiceInfos = list;
+                        // This callback may occur after the dialog has been shown.
+                        // If so, add controls into the already visible space
+                        if (mDialog != null && !mDialog.isShowingControls()
+                                && shouldShowControls()) {
+                            mDialog.showControls(mControlsUiControllerOptional.get());
+                        }
+                    });
         }
 
         // Need to be user-specific with the context to make sure we read the correct prefs
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 5595201..f039fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -17,12 +17,8 @@
 package com.android.systemui.media;
 
 import android.app.PendingIntent;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -35,7 +31,6 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
-import android.service.media.MediaBrowserService;
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageButton;
@@ -54,16 +49,17 @@
 import com.android.settingslib.media.MediaOutputSliceConstants;
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.QSMediaBrowser;
 import com.android.systemui.util.animation.TransitionLayout;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+
 /**
  * A view controller used for Media Playback.
  */
@@ -81,7 +77,6 @@
 
     private final SeekBarViewModel mSeekBarViewModel;
     private SeekBarObserver mSeekBarObserver;
-    private final Executor mForegroundExecutor;
     protected final Executor mBackgroundExecutor;
     private final ActivityStarter mActivityStarter;
 
@@ -91,51 +86,23 @@
     private MediaSession.Token mToken;
     private MediaController mController;
     private int mBackgroundColor;
-    protected ComponentName mServiceComponent;
-    private boolean mIsRegistered = false;
-    private String mKey;
     private int mAlbumArtSize;
     private int mAlbumArtRadius;
-    private int mViewWidth;
-
-    public static final String MEDIA_PREFERENCES = "media_control_prefs";
-    public static final String MEDIA_PREFERENCE_KEY = "browser_components";
-    private SharedPreferences mSharedPrefs;
-    private boolean mCheckedForResumption = false;
-    private QSMediaBrowser mQSMediaBrowser;
-
-    private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
-        @Override
-        public void onSessionDestroyed() {
-            Log.d(TAG, "session destroyed");
-            mController.unregisterCallback(mSessionCallback);
-            clearControls();
-        }
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            final int s = state != null ? state.getState() : PlaybackState.STATE_NONE;
-            if (s == PlaybackState.STATE_NONE) {
-                Log.d(TAG, "playback state change will trigger resumption, state=" + state);
-                clearControls();
-            }
-        }
-    };
 
     /**
      * Initialize a new control panel
      * @param context
-     * @param foregroundExecutor foreground executor
      * @param backgroundExecutor background executor, used for processing artwork
      * @param activityStarter activity starter
      */
-    public MediaControlPanel(Context context, Executor foregroundExecutor,
-            DelayableExecutor backgroundExecutor, ActivityStarter activityStarter,
-            MediaHostStatesManager mediaHostStatesManager) {
+    @Inject
+    public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
+            ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager,
+            SeekBarViewModel seekBarViewModel) {
         mContext = context;
-        mForegroundExecutor = foregroundExecutor;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
-        mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
+        mSeekBarViewModel = seekBarViewModel;
         mMediaViewController = new MediaViewController(context, mediaHostStatesManager);
         loadDimens();
     }
@@ -214,45 +181,18 @@
         MediaSession.Token token = data.getToken();
         mBackgroundColor = data.getBackgroundColor();
         if (mToken == null || !mToken.equals(token)) {
-            if (mQSMediaBrowser != null) {
-                Log.d(TAG, "Disconnecting old media browser");
-                mQSMediaBrowser.disconnect();
-                mQSMediaBrowser = null;
-            }
             mToken = token;
-            mServiceComponent = null;
-            mCheckedForResumption = false;
         }
 
-        mController = new MediaController(mContext, mToken);
+        if (mToken != null) {
+            mController = new MediaController(mContext, mToken);
+        } else {
+            mController = null;
+        }
 
         ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
         ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
 
-        // Try to find a browser service component for this app
-        // TODO also check for a media button receiver intended for restarting (b/154127084)
-        // Only check if we haven't tried yet or the session token changed
-        final String pkgName = data.getPackageName();
-        if (mServiceComponent == null && !mCheckedForResumption) {
-            Log.d(TAG, "Checking for service component");
-            PackageManager pm = mContext.getPackageManager();
-            Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
-            List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0);
-            // TODO: look into this resumption
-            if (resumeInfo != null) {
-                for (ResolveInfo inf : resumeInfo) {
-                    if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
-                        mBackgroundExecutor.execute(() ->
-                                tryUpdateResumptionList(inf.getComponentInfo().getComponentName()));
-                        break;
-                    }
-                }
-            }
-            mCheckedForResumption = true;
-        }
-
-        mController.registerCallback(mSessionCallback);
-
         mViewHolder.getPlayer().setBackgroundTintList(
                 ColorStateList.valueOf(mBackgroundColor));
 
@@ -267,12 +207,22 @@
         ImageView albumView = mViewHolder.getAlbumView();
         // TODO: migrate this to a view with rounded corners instead of baking the rounding
         // into the bitmap
-        Drawable artwork = createRoundedBitmap(data.getArtwork());
-        albumView.setImageDrawable(artwork);
+        boolean hasArtwork = data.getArtwork() != null;
+        if (hasArtwork) {
+            Drawable artwork = createRoundedBitmap(data.getArtwork());
+            albumView.setImageDrawable(artwork);
+        }
+        setVisibleAndAlpha(collapsedSet, R.id.album_art, hasArtwork);
+        setVisibleAndAlpha(expandedSet, R.id.album_art, hasArtwork);
 
         // App icon
         ImageView appIcon = mViewHolder.getAppIcon();
-        appIcon.setImageDrawable(data.getAppIcon());
+        if (data.getAppIcon() != null) {
+            appIcon.setImageDrawable(data.getAppIcon());
+        } else {
+            Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_music_note);
+            appIcon.setImageDrawable(iconDrawable);
+        }
 
         // Song name
         TextView titleText = mViewHolder.getTitleText();
@@ -294,7 +244,7 @@
             final Intent intent = new Intent()
                     .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
                     .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
-                            mController.getPackageName())
+                            data.getPackageName())
                     .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
             mActivityStarter.startActivity(intent, false, true /* dismissShade */,
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -350,15 +300,11 @@
             MediaAction mediaAction = actionIcons.get(i);
             button.setImageDrawable(mediaAction.getDrawable());
             button.setContentDescription(mediaAction.getContentDescription());
-            PendingIntent actionIntent = mediaAction.getIntent();
+            Runnable action = mediaAction.getAction();
 
             button.setOnClickListener(v -> {
-                if (actionIntent != null) {
-                    try {
-                        actionIntent.send();
-                    } catch (PendingIntent.CanceledException e) {
-                        e.printStackTrace();
-                    }
+                if (action != null) {
+                    action.run();
                 }
             });
             boolean visibleInCompat = actionsWhenCollapsed.contains(i);
@@ -444,14 +390,6 @@
     }
 
     /**
-     * Return the original notification's key
-     * @return The notification key
-     */
-    public String getKey()  {
-        return mKey;
-    }
-
-    /**
      * Check whether this player has an attached media session.
      * @return whether there is a controller with a current media session.
      */
@@ -485,150 +423,8 @@
         return (state.getState() == PlaybackState.STATE_PLAYING);
     }
 
-    /**
-     * Puts controls into a resumption state if possible, or calls removePlayer if no component was
-     * found that could resume playback
-     */
-    public void clearControls() {
-        Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage());
-        if (mServiceComponent == null) {
-            // If we don't have a way to resume, just remove the player altogether
-            Log.d(TAG, "Removing unresumable controls");
-            removePlayer();
-            return;
-        }
-        resetButtons();
-    }
-
-    /**
-     * Hide the media buttons and show only a restart button
-     */
-    protected void resetButtons() {
-        if (mViewHolder == null) {
-            return;
-        }
-        // Hide all the old buttons
-
-        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
-        ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
-        for (int i = 1; i < ACTION_IDS.length; i++) {
-            setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */);
-            setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */);
-        }
-
-        // Add a restart button
-        ImageButton btn = mViewHolder.getAction0();
-        btn.setOnClickListener(v -> {
-            Log.d(TAG, "Attempting to restart session");
-            if (mQSMediaBrowser != null) {
-                mQSMediaBrowser.disconnect();
-            }
-            mQSMediaBrowser = new QSMediaBrowser(mContext, new QSMediaBrowser.Callback(){
-                @Override
-                public void onConnected() {
-                    Log.d(TAG, "Successfully restarted");
-                }
-                @Override
-                public void onError() {
-                    Log.e(TAG, "Error restarting");
-                    mQSMediaBrowser.disconnect();
-                    mQSMediaBrowser = null;
-                }
-            }, mServiceComponent);
-            mQSMediaBrowser.restart();
-        });
-        btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
-        setVisibleAndAlpha(expandedSet, ACTION_IDS[0], true /*visible */);
-        setVisibleAndAlpha(collapsedSet, ACTION_IDS[0], true /*visible */);
-
-        mSeekBarViewModel.clearController();
-        // TODO: fix guts
-        //        View guts = mMediaNotifView.findViewById(R.id.media_guts);
-        View options = mViewHolder.getOptions();
-
-        mViewHolder.getPlayer().setOnLongClickListener(v -> {
-            // Replace player view with close/cancel view
-//            guts.setVisibility(View.GONE);
-            options.setVisibility(View.VISIBLE);
-            return true; // consumed click
-        });
-        mMediaViewController.refreshState();
-    }
-
     private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) {
         set.setVisibility(actionId, visible? ConstraintSet.VISIBLE : ConstraintSet.GONE);
         set.setAlpha(actionId, visible ? 1.0f : 0.0f);
     }
-
-    /**
-     * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
-     * component to the list of resumption components
-     */
-    private void tryUpdateResumptionList(ComponentName componentName) {
-        Log.d(TAG, "Testing if we can connect to " + componentName);
-        if (mQSMediaBrowser != null) {
-            mQSMediaBrowser.disconnect();
-        }
-        mQSMediaBrowser = new QSMediaBrowser(mContext,
-                new QSMediaBrowser.Callback() {
-                    @Override
-                    public void onConnected() {
-                        Log.d(TAG, "yes we can resume with " + componentName);
-                        mServiceComponent = componentName;
-                        updateResumptionList(componentName);
-                        mQSMediaBrowser.disconnect();
-                        mQSMediaBrowser = null;
-                    }
-
-                    @Override
-                    public void onError() {
-                        Log.d(TAG, "Cannot resume with " + componentName);
-                        mServiceComponent = null;
-                        if (!hasMediaSession()) {
-                            // If it's not active and we can't resume, remove
-                            removePlayer();
-                        }
-                        mQSMediaBrowser.disconnect();
-                        mQSMediaBrowser = null;
-                    }
-                },
-                componentName);
-        mQSMediaBrowser.testConnection();
-    }
-
-    /**
-     * Add the component to the saved list of media browser services, checking for duplicates and
-     * removing older components that exceed the maximum limit
-     * @param componentName
-     */
-    private synchronized void updateResumptionList(ComponentName componentName) {
-        // Add to front of saved list
-        if (mSharedPrefs == null) {
-            mSharedPrefs = mContext.getSharedPreferences(MEDIA_PREFERENCES, 0);
-        }
-        String componentString = componentName.flattenToString();
-        String listString = mSharedPrefs.getString(MEDIA_PREFERENCE_KEY, null);
-        if (listString == null) {
-            listString = componentString;
-        } else {
-            String[] components = listString.split(QSMediaBrowser.DELIMITER);
-            StringBuilder updated = new StringBuilder(componentString);
-            int nBrowsers = 1;
-            for (int i = 0; i < components.length
-                    && nBrowsers < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
-                if (componentString.equals(components[i])) {
-                    continue;
-                }
-                updated.append(QSMediaBrowser.DELIMITER).append(components[i]);
-                nBrowsers++;
-            }
-            listString = updated.toString();
-        }
-        mSharedPrefs.edit().putString(MEDIA_PREFERENCE_KEY, listString).apply();
-    }
-
-    /**
-     * Called when a player can't be resumed to give it an opportunity to hide or remove itself
-     */
-    protected void removePlayer() { }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index a94f6a8..5d28178 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -32,17 +32,19 @@
     val artwork: Icon?,
     val actions: List<MediaAction>,
     val actionsToShowInCompact: List<Int>,
-    val packageName: String?,
+    val packageName: String,
     val token: MediaSession.Token?,
     val clickIntent: PendingIntent?,
     val device: MediaDeviceData?,
-    val notificationKey: String = "INVALID"
+    var resumeAction: Runnable?,
+    val notificationKey: String = "INVALID",
+    var hasCheckedForResume: Boolean = false
 )
 
 /** State of a media action. */
 data class MediaAction(
     val drawable: Drawable?,
-    val intent: PendingIntent?,
+    val action: Runnable?,
     val contentDescription: CharSequence?
 )
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index 67cf21a..11cbc48 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -32,9 +32,15 @@
 
     init {
         dataSource.addListener(object : MediaDataManager.Listener {
-            override fun onMediaDataLoaded(key: String, data: MediaData) {
-                entries[key] = data to entries[key]?.second
-                update(key)
+            override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+                if (oldKey != null && !oldKey.equals(key)) {
+                    val s = entries[oldKey]?.second
+                    entries[key] = data to entries[oldKey]?.second
+                    entries.remove(oldKey)
+                } else {
+                    entries[key] = data to entries[key]?.second
+                }
+                update(key, oldKey)
             }
             override fun onMediaDataRemoved(key: String) {
                 remove(key)
@@ -43,7 +49,7 @@
         deviceSource.addListener(object : MediaDeviceManager.Listener {
             override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) {
                 entries[key] = entries[key]?.first to data
-                update(key)
+                update(key, key)
             }
             override fun onKeyRemoved(key: String) {
                 remove(key)
@@ -61,13 +67,13 @@
      */
     fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
 
-    private fun update(key: String) {
+    private fun update(key: String, oldKey: String?) {
         val (entry, device) = entries[key] ?: null to null
         if (entry != null && device != null) {
             val data = entry.copy(device = device)
             val listenersCopy = listeners.toSet()
             listenersCopy.forEach {
-                it.onMediaDataLoaded(key, data)
+                it.onMediaDataLoaded(key, oldKey, data)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index d949857..094c5be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media
 
 import android.app.Notification
+import android.app.PendingIntent
 import android.content.ContentResolver
 import android.content.Context
 import android.graphics.Bitmap
@@ -25,6 +26,7 @@
 import android.graphics.ImageDecoder
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
+import android.media.MediaDescription
 import android.media.MediaMetadata
 import android.media.session.MediaSession
 import android.net.Uri
@@ -32,8 +34,10 @@
 import android.text.TextUtils
 import android.util.Log
 import com.android.internal.graphics.ColorUtils
+import com.android.systemui.R
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON
@@ -58,7 +62,7 @@
 private const val SATURATION_MULTIPLIER = 0.8f
 
 private val LOADING = MediaData(false, 0, null, null, null, null, null,
-        emptyList(), emptyList(), null, null, null, null)
+        emptyList(), emptyList(), "INVALID", null, null, null, null)
 
 fun isMediaNotification(sbn: StatusBarNotification): Boolean {
     if (!sbn.notification.hasMediaSession()) {
@@ -81,34 +85,92 @@
     private val mediaControllerFactory: MediaControllerFactory,
     private val mediaTimeoutListener: MediaTimeoutListener,
     private val notificationEntryManager: NotificationEntryManager,
+    private val mediaResumeListener: MediaResumeListener,
     @Background private val backgroundExecutor: Executor,
     @Main private val foregroundExecutor: Executor
 ) {
 
     private val listeners: MutableSet<Listener> = mutableSetOf()
     private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    private val useMediaResumption: Boolean = Utils.useMediaResumption(context)
 
     init {
         mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
             setTimedOut(token, timedOut) }
         addListener(mediaTimeoutListener)
+
+        if (useMediaResumption) {
+            mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription,
+                resumeAction: Runnable, token: MediaSession.Token, appName: String,
+                appIntent: PendingIntent, packageName: String ->
+                addResumptionControls(desc, resumeAction, token, appName, appIntent, packageName)
+            }
+            mediaResumeListener.resumeComponentFoundCallback = { key: String, action: Runnable? ->
+                mediaEntries.get(key)?.resumeAction = action
+                mediaEntries.get(key)?.hasCheckedForResume = true
+            }
+            addListener(mediaResumeListener)
+        }
     }
 
     fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
         if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
             Assert.isMainThread()
-            if (!mediaEntries.containsKey(key)) {
-                mediaEntries.put(key, LOADING)
+            val oldKey = findExistingEntry(key, sbn.packageName)
+            if (oldKey == null) {
+                val temp = LOADING.copy(packageName = sbn.packageName)
+                mediaEntries.put(key, temp)
+            } else if (oldKey != key) {
+                // Move to new key
+                val oldData = mediaEntries.remove(oldKey)!!
+                mediaEntries.put(key, oldData)
             }
-            loadMediaData(key, sbn)
+            loadMediaData(key, sbn, oldKey)
         } else {
             onNotificationRemoved(key)
         }
     }
 
-    private fun loadMediaData(key: String, sbn: StatusBarNotification) {
+    private fun addResumptionControls(
+        desc: MediaDescription,
+        action: Runnable,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ) {
+        // Resume controls don't have a notification key, so store by package name instead
+        if (!mediaEntries.containsKey(packageName)) {
+            val resumeData = LOADING.copy(packageName = packageName, resumeAction = action)
+            mediaEntries.put(packageName, resumeData)
+        }
         backgroundExecutor.execute {
-            loadMediaDataInBg(key, sbn)
+            loadMediaDataInBg(desc, action, token, appName, appIntent, packageName)
+        }
+    }
+
+    /**
+     * Check if there is an existing entry that matches the key or package name.
+     * Returns the key that matches, or null if not found.
+     */
+    private fun findExistingEntry(key: String, packageName: String): String? {
+        if (mediaEntries.containsKey(key)) {
+            return key
+        }
+        // Check if we already had a resume player
+        if (mediaEntries.containsKey(packageName)) {
+            return packageName
+        }
+        return null
+    }
+
+    private fun loadMediaData(
+        key: String,
+        sbn: StatusBarNotification,
+        oldKey: String?
+    ) {
+        backgroundExecutor.execute {
+            loadMediaDataInBg(key, sbn, oldKey)
         }
     }
 
@@ -132,7 +194,50 @@
         }
     }
 
-    private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) {
+    private fun loadMediaDataInBg(
+        desc: MediaDescription,
+        resumeAction: Runnable,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ) {
+        if (resumeAction == null) {
+            Log.e(TAG, "Resume action cannot be null")
+            return
+        }
+
+        if (TextUtils.isEmpty(desc.title)) {
+            Log.e(TAG, "Description incomplete")
+            return
+        }
+
+        Log.d(TAG, "adding track from browser: $desc")
+
+        // Album art
+        var artworkBitmap = desc.iconBitmap
+        if (artworkBitmap == null && desc.iconUri != null) {
+            artworkBitmap = loadBitmapFromUri(desc.iconUri!!)
+        }
+        val artworkIcon = if (artworkBitmap != null) {
+            Icon.createWithBitmap(artworkBitmap)
+        } else {
+            null
+        }
+
+        val mediaAction = getResumeMediaAction(resumeAction)
+        foregroundExecutor.execute {
+            onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName,
+                null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
+                packageName, token, appIntent, null, resumeAction, packageName))
+        }
+    }
+
+    private fun loadMediaDataInBg(
+        key: String,
+        sbn: StatusBarNotification,
+        oldKey: String?
+    ) {
         val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
                 as MediaSession.Token?
         val metadata = mediaControllerFactory.create(token).metadata
@@ -234,16 +339,23 @@
                 }
                 val mediaAction = MediaAction(
                         action.getIcon().loadDrawable(packageContext),
-                        action.actionIntent,
+                        Runnable {
+                            try {
+                                action.actionIntent.send()
+                            } catch (e: PendingIntent.CanceledException) {
+                                Log.d(TAG, "Intent canceled", e)
+                            }
+                        },
                         action.title)
                 actionIcons.add(mediaAction)
             }
         }
 
+        val resumeAction: Runnable? = mediaEntries.get(key)?.resumeAction
         foregroundExecutor.execute {
-            onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song,
-                    artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
-                    notif.contentIntent, null, key))
+            onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
+                    song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
+                    notif.contentIntent, null, resumeAction, key))
         }
     }
 
@@ -257,7 +369,7 @@
                 val albumArt = loadBitmapFromUri(Uri.parse(uriString))
                 if (albumArt != null) {
                     Log.d(TAG, "loaded art from $uri")
-                    break
+                    return albumArt
                 }
             }
         }
@@ -283,27 +395,52 @@
 
         val source = ImageDecoder.createSource(context.getContentResolver(), uri)
         return try {
-            ImageDecoder.decodeBitmap(source)
+            ImageDecoder.decodeBitmap(source) {
+                decoder, info, source -> decoder.isMutableRequired = true
+            }
         } catch (e: IOException) {
             e.printStackTrace()
             null
         }
     }
 
-    fun onMediaDataLoaded(key: String, data: MediaData) {
+    private fun getResumeMediaAction(action: Runnable): MediaAction {
+        return MediaAction(
+            context.getDrawable(R.drawable.lb_ic_play),
+            action,
+            context.getString(R.string.controls_media_resume)
+        )
+    }
+
+    fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
         Assert.isMainThread()
         if (mediaEntries.containsKey(key)) {
             // Otherwise this was removed already
             mediaEntries.put(key, data)
             val listenersCopy = listeners.toSet()
             listenersCopy.forEach {
-                it.onMediaDataLoaded(key, data)
+                it.onMediaDataLoaded(key, oldKey, data)
             }
         }
     }
 
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
+        if (useMediaResumption && mediaEntries.get(key)?.resumeAction != null) {
+            Log.d(TAG, "Not removing $key because resumable")
+            // Move to resume key aka package name
+            val data = mediaEntries.remove(key)!!
+            val resumeAction = getResumeMediaAction(data.resumeAction!!)
+            val updated = data.copy(token = null, actions = listOf(resumeAction),
+                actionsToShowInCompact = listOf(0))
+            mediaEntries.put(data.packageName, updated)
+            // Notify listeners of "new" controls
+            val listenersCopy = listeners.toSet()
+            listenersCopy.forEach {
+                it.onMediaDataLoaded(data.packageName, key, updated)
+            }
+            return
+        }
         val removed = mediaEntries.remove(key)
         if (removed != null) {
             val listenersCopy = listeners.toSet()
@@ -316,19 +453,32 @@
     /**
      * Are there any media notifications active?
      */
-    fun hasActiveMedia() = mediaEntries.isNotEmpty()
+    fun hasActiveMedia() = mediaEntries.any({ isActive(it.value) })
 
-    fun hasAnyMedia(): Boolean {
-        // TODO: implement this when we implemented resumption
-        return hasActiveMedia()
+    fun isActive(data: MediaData): Boolean {
+        if (data.token == null) {
+            return false
+        }
+        val controller = mediaControllerFactory.create(data.token)
+        val state = controller?.playbackState?.state
+        return state != null && NotificationMediaManager.isActiveState(state)
     }
 
+    /**
+     * Are there any media entries, including resume controls?
+     */
+    fun hasAnyMedia() = mediaEntries.isNotEmpty()
+
     interface Listener {
 
         /**
-         * Called whenever there's new MediaData Loaded for the consumption in views
+         * Called whenever there's new MediaData Loaded for the consumption in views.
+         *
+         * oldKey is provided to check whether the view has changed keys, which can happen when a
+         * player has gone from resume state (key is package name) to active state (key is
+         * notification key) or vice versa.
          */
-        fun onMediaDataLoaded(key: String, data: MediaData) {}
+        fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {}
 
         /**
          * Called whenever a previously existing Media notification was removed
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 552fea6..2f521ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -16,11 +16,8 @@
 
 package com.android.systemui.media
 
-import android.app.Notification
 import android.content.Context
-import android.service.notification.StatusBarNotification
 import android.media.MediaRouter2Manager
-import android.media.session.MediaSession
 import android.media.session.MediaController
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
@@ -38,11 +35,16 @@
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     private val mr2manager: MediaRouter2Manager,
     private val featureFlag: MediaFeatureFlag,
-    @Main private val fgExecutor: Executor
-) {
+    @Main private val fgExecutor: Executor,
+    private val mediaDataManager: MediaDataManager
+) : MediaDataManager.Listener {
     private val listeners: MutableSet<Listener> = mutableSetOf()
     private val entries: MutableMap<String, Token> = mutableMapOf()
 
+    init {
+        mediaDataManager.addListener(this)
+    }
+
     /**
      * Add a listener for changes to the media route (ie. device).
      */
@@ -53,23 +55,25 @@
      */
     fun removeListener(listener: Listener) = listeners.remove(listener)
 
-    fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
-        if (featureFlag.enabled && isMediaNotification(sbn)) {
+    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (featureFlag.enabled) {
+            if (oldKey != null && oldKey != key) {
+                val oldToken = entries.remove(oldKey)
+                oldToken?.stop()
+            }
             var tok = entries[key]
-            if (tok == null) {
-                val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
-                        as MediaSession.Token?
-                val controller = MediaController(context, token)
-                tok = Token(key, controller, localMediaManagerFactory.create(sbn.packageName))
+            if (tok == null && data.token != null) {
+                val controller = MediaController(context, data.token!!)
+                tok = Token(key, controller, localMediaManagerFactory.create(data.packageName))
                 entries[key] = tok
                 tok.start()
             }
         } else {
-            onNotificationRemoved(key)
+            onMediaDataRemoved(key)
         }
     }
 
-    fun onNotificationRemoved(key: String) {
+    override fun onMediaDataRemoved(key: String) {
         val token = entries.remove(key)
         token?.stop()
         token?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index e904e93..2bd8c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -50,7 +50,7 @@
         }
 
     private val listener = object : MediaDataManager.Listener {
-        override fun onMediaDataLoaded(key: String, data: MediaData) {
+        override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
             updateViewVisibility()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
new file mode 100644
index 0000000..6bbe0d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.media.MediaDescription
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.UserHandle
+import android.service.media.MediaBrowserService
+import android.util.Log
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.Utils
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val TAG = "MediaResumeListener"
+
+private const val MEDIA_PREFERENCES = "media_control_prefs"
+private const val MEDIA_PREFERENCE_KEY = "browser_components"
+
+@Singleton
+class MediaResumeListener @Inject constructor(
+    private val context: Context,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    @Background private val backgroundExecutor: Executor
+) : MediaDataManager.Listener {
+
+    private val useMediaResumption: Boolean = Utils.useMediaResumption(context)
+    private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
+
+    lateinit var addTrackToResumeCallback: (
+        MediaDescription,
+        Runnable,
+        MediaSession.Token,
+        String,
+        PendingIntent,
+        String
+    ) -> Unit
+    lateinit var resumeComponentFoundCallback: (String, Runnable?) -> Unit
+
+    private var mediaBrowser: ResumeMediaBrowser? = null
+
+    private val unlockReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (Intent.ACTION_USER_UNLOCKED == intent.action) {
+                loadMediaResumptionControls()
+            }
+        }
+    }
+
+    private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() {
+        override fun addTrack(
+            desc: MediaDescription,
+            component: ComponentName,
+            browser: ResumeMediaBrowser
+        ) {
+            val token = browser.token
+            val appIntent = browser.appIntent
+            val pm = context.getPackageManager()
+            var appName: CharSequence = component.packageName
+            val resumeAction = getResumeAction(component)
+            try {
+                appName = pm.getApplicationLabel(
+                        pm.getApplicationInfo(component.packageName, 0))
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(TAG, "Error getting package information", e)
+            }
+
+            Log.d(TAG, "Adding resume controls $desc")
+            addTrackToResumeCallback(desc, resumeAction, token, appName.toString(), appIntent,
+                component.packageName)
+        }
+    }
+
+    init {
+        if (useMediaResumption) {
+            val unlockFilter = IntentFilter()
+            unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
+            broadcastDispatcher.registerReceiver(unlockReceiver, unlockFilter, null, UserHandle.ALL)
+            loadSavedComponents()
+        }
+    }
+
+    private fun loadSavedComponents() {
+        val userContext = context.createContextAsUser(context.getUser(), 0)
+        val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
+        val listString = prefs.getString(MEDIA_PREFERENCE_KEY, null)
+        val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
+            ?.dropLastWhile { it.isEmpty() }
+        components?.forEach {
+            val info = it.split("/")
+            val packageName = info[0]
+            val className = info[1]
+            val component = ComponentName(packageName, className)
+            resumeComponents.add(component)
+        }
+        Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
+    }
+
+    /**
+     * Load controls for resuming media, if available
+     */
+    private fun loadMediaResumptionControls() {
+        if (!useMediaResumption) {
+            return
+        }
+
+        resumeComponents.forEach {
+            val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
+            browser.findRecentMedia()
+        }
+        broadcastDispatcher.unregisterReceiver(unlockReceiver) // only need to load once
+    }
+
+    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (useMediaResumption) {
+            // If this had been started from a resume state, disconnect now that it's live
+            mediaBrowser?.disconnect()
+            // If we don't have a resume action, check if we haven't already
+            if (data.resumeAction == null && !data.hasCheckedForResume) {
+                // TODO also check for a media button receiver intended for restarting (b/154127084)
+                Log.d(TAG, "Checking for service component for " + data.packageName)
+                val pm = context.packageManager
+                val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
+                val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
+
+                val inf = resumeInfo?.filter {
+                    it.serviceInfo.packageName == data.packageName
+                }
+                if (inf != null && inf.size > 0) {
+                    backgroundExecutor.execute {
+                        tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
+                    }
+                } else {
+                    // No service found
+                    resumeComponentFoundCallback(key, null)
+                }
+            }
+        }
+    }
+
+    /**
+     * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
+     * component to the list of resumption components
+     */
+    private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
+        Log.d(TAG, "Testing if we can connect to $componentName")
+        mediaBrowser?.disconnect()
+        mediaBrowser = ResumeMediaBrowser(context,
+                object : ResumeMediaBrowser.Callback() {
+                    override fun onConnected() {
+                        Log.d(TAG, "yes we can resume with $componentName")
+                        resumeComponentFoundCallback(key, getResumeAction(componentName))
+                        updateResumptionList(componentName)
+                        mediaBrowser?.disconnect()
+                        mediaBrowser = null
+                    }
+
+                    override fun onError() {
+                        Log.e(TAG, "Cannot resume with $componentName")
+                        resumeComponentFoundCallback(key, null)
+                        mediaBrowser?.disconnect()
+                        mediaBrowser = null
+                    }
+                },
+                componentName)
+        mediaBrowser?.testConnection()
+    }
+
+    /**
+     * Add the component to the saved list of media browser services, checking for duplicates and
+     * removing older components that exceed the maximum limit
+     * @param componentName
+     */
+    private fun updateResumptionList(componentName: ComponentName) {
+        // Remove if exists
+        resumeComponents.remove(componentName)
+        // Insert at front of queue
+        resumeComponents.add(componentName)
+        // Remove old components if over the limit
+        if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) {
+            resumeComponents.remove()
+        }
+
+        // Save changes
+        val sb = StringBuilder()
+        resumeComponents.forEach {
+            sb.append(it.flattenToString())
+            sb.append(ResumeMediaBrowser.DELIMITER)
+        }
+        val userContext = context.createContextAsUser(context.getUser(), 0)
+        val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
+        prefs.edit().putString(MEDIA_PREFERENCE_KEY, sb.toString()).apply()
+    }
+
+    /**
+     * Get a runnable which will resume media playback
+     */
+    private fun getResumeAction(componentName: ComponentName): Runnable {
+        return Runnable {
+            mediaBrowser?.disconnect()
+            mediaBrowser = ResumeMediaBrowser(context,
+                object : ResumeMediaBrowser.Callback() {
+                    override fun onConnected() {
+                        if (mediaBrowser?.token == null) {
+                            Log.e(TAG, "Error after connect")
+                            mediaBrowser?.disconnect()
+                            mediaBrowser = null
+                            return
+                        }
+                        Log.d(TAG, "Connected for restart $componentName")
+                        val controller = MediaController(context, mediaBrowser!!.token)
+                        val controls = controller.transportControls
+                        controls.prepare()
+                        controls.play()
+                    }
+
+                    override fun onError() {
+                        Log.e(TAG, "Resume failed for $componentName")
+                        mediaBrowser?.disconnect()
+                        mediaBrowser = null
+                    }
+                },
+                componentName)
+            mediaBrowser?.restart()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 92a1ab1..3c3f4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -45,7 +45,7 @@
 
     lateinit var timeoutCallback: (String, Boolean) -> Unit
 
-    override fun onMediaDataLoaded(key: String, data: MediaData) {
+    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
         if (mediaListeners.containsKey(key)) {
             return
         }
@@ -67,27 +67,35 @@
 
         var timedOut = false
 
-        private val mediaController = mediaControllerFactory.create(data.token)
+        // Resume controls may have null token
+        private val mediaController = if (data.token != null) {
+            mediaControllerFactory.create(data.token)
+        } else {
+            null
+        }
         private var cancellation: Runnable? = null
 
         init {
-            mediaController.registerCallback(this)
+            mediaController?.registerCallback(this)
         }
 
         fun destroy() {
-            mediaController.unregisterCallback(this)
+            mediaController?.unregisterCallback(this)
         }
 
         override fun onPlaybackStateChanged(state: PlaybackState?) {
             if (DEBUG) {
                 Log.v(TAG, "onPlaybackStateChanged: $state")
             }
-            expireMediaTimeout(key, "playback state ativity - $state, $key")
 
             if (state == null || !isPlayingState(state.state)) {
                 if (DEBUG) {
                     Log.v(TAG, "schedule timeout for $key")
                 }
+                if (cancellation != null) {
+                    if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
+                    return
+                }
                 expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
                 cancellation = mainExecutor.executeDelayed({
                     cancellation = null
@@ -98,6 +106,7 @@
                     timeoutCallback(key, timedOut)
                 }, PAUSED_MEDIA_TIMEOUT)
             } else {
+                expireMediaTimeout(key, "playback started - $state, $key")
                 timedOut = false
                 timeoutCallback(key, timedOut)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 8ab30c7..9b9a6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -11,16 +11,12 @@
 import android.widget.LinearLayout
 import androidx.core.view.GestureDetectorCompat
 import com.android.systemui.R
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.statusbar.notification.VisualStabilityManager
 import com.android.systemui.util.animation.UniqueObjectHostView
 import com.android.systemui.util.animation.requiresRemeasuring
-import com.android.systemui.util.concurrency.DelayableExecutor
-import java.util.concurrent.Executor
 import javax.inject.Inject
+import javax.inject.Provider
 import javax.inject.Singleton
 
 private const val FLING_SLOP = 1000000
@@ -32,10 +28,8 @@
 @Singleton
 class MediaViewManager @Inject constructor(
     private val context: Context,
-    @Main private val foregroundExecutor: Executor,
-    @Background private val backgroundExecutor: DelayableExecutor,
+    private val mediaControlPanelFactory: Provider<MediaControlPanel>,
     private val visualStabilityManager: VisualStabilityManager,
-    private val activityStarter: ActivityStarter,
     private val mediaHostStatesManager: MediaHostStatesManager,
     mediaManager: MediaDataCombineLatest
 ) {
@@ -147,8 +141,8 @@
         visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback,
                 true /* persistent */)
         mediaManager.addListener(object : MediaDataManager.Listener {
-            override fun onMediaDataLoaded(key: String, data: MediaData) {
-                updateView(key, data)
+            override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+                updateView(key, oldKey, data)
                 updatePlayerVisibilities()
                 mediaCarousel.requiresRemeasuring = true
             }
@@ -259,11 +253,16 @@
         }
     }
 
-    private fun updateView(key: String, data: MediaData) {
+    private fun updateView(key: String, oldKey: String?, data: MediaData) {
+        // If the key was changed, update entry
+        val oldData = mediaPlayers[oldKey]
+        if (oldData != null) {
+            val oldData = mediaPlayers.remove(oldKey)
+            mediaPlayers.put(key, oldData!!)
+        }
         var existingPlayer = mediaPlayers[key]
         if (existingPlayer == null) {
-            existingPlayer = MediaControlPanel(context, foregroundExecutor, backgroundExecutor,
-                    activityStarter, mediaHostStatesManager)
+            existingPlayer = mediaControlPanelFactory.get()
             existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
                     mediaContent))
             mediaPlayers[key] = existingPlayer
@@ -286,7 +285,7 @@
                 needsReordering = true
             }
         }
-        existingPlayer.bind(data)
+        existingPlayer?.bind(data)
         updateMediaPaddings()
         updatePageIndicator()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
rename to packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index a5b73dc..1e9a303 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs;
+package com.android.systemui.media;
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -27,14 +27,17 @@
 import android.media.session.MediaSession;
 import android.os.Bundle;
 import android.service.media.MediaBrowserService;
+import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.systemui.util.Utils;
+
 import java.util.List;
 
 /**
- * Media browser for managing resumption in QS media controls
+ * Media browser for managing resumption in media controls
  */
-public class QSMediaBrowser {
+public class ResumeMediaBrowser {
 
     /** Maximum number of controls to show on boot */
     public static final int MAX_RESUMPTION_CONTROLS = 5;
@@ -42,7 +45,8 @@
     /** Delimiter for saved component names */
     public static final String DELIMITER = ":";
 
-    private static final String TAG = "QSMediaBrowser";
+    private static final String TAG = "ResumeMediaBrowser";
+    private boolean mIsEnabled = false;
     private final Context mContext;
     private final Callback mCallback;
     private MediaBrowser mMediaBrowser;
@@ -54,21 +58,25 @@
      * @param callback used to report media items found
      * @param componentName Component name of the MediaBrowserService this browser will connect to
      */
-    public QSMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+    public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+        mIsEnabled = Utils.useMediaResumption(context);
         mContext = context;
         mCallback = callback;
         mComponentName = componentName;
     }
 
     /**
-     * Connects to the MediaBrowserService and looks for valid media. If a media item is returned
-     * by the service, QSMediaBrowser.Callback#addTrack will be called with its MediaDescription.
-     * QSMediaBrowser.Callback#onConnected and QSMediaBrowser.Callback#onError will also be called
-     * when the initial connection is successful, or an error occurs. Note that it is possible for
-     * the service to connect but for no playable tracks to be found later.
-     * QSMediaBrowser#disconnect will be called automatically with this function.
+     * Connects to the MediaBrowserService and looks for valid media. If a media item is returned,
+     * ResumeMediaBrowser.Callback#addTrack will be called with the MediaDescription.
+     * ResumeMediaBrowser.Callback#onConnected and ResumeMediaBrowser.Callback#onError will also be
+     * called when the initial connection is successful, or an error occurs.
+     * Note that it is possible for the service to connect but for no playable tracks to be found.
+     * ResumeMediaBrowser#disconnect will be called automatically with this function.
      */
     public void findRecentMedia() {
+        if (!mIsEnabled) {
+            return;
+        }
         Log.d(TAG, "Connecting to " + mComponentName);
         disconnect();
         Bundle rootHints = new Bundle();
@@ -86,7 +94,7 @@
         public void onChildrenLoaded(String parentId,
                 List<MediaBrowser.MediaItem> children) {
             if (children.size() == 0) {
-                Log.e(TAG, "No children found for " + mComponentName);
+                Log.d(TAG, "No children found for " + mComponentName);
                 return;
             }
             // We ask apps to return a playable item as the first child when sending
@@ -94,23 +102,24 @@
             MediaBrowser.MediaItem child = children.get(0);
             MediaDescription desc = child.getDescription();
             if (child.isPlayable()) {
-                mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), QSMediaBrowser.this);
+                mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
+                        ResumeMediaBrowser.this);
             } else {
-                Log.e(TAG, "Child found but not playable for " + mComponentName);
+                Log.d(TAG, "Child found but not playable for " + mComponentName);
             }
             disconnect();
         }
 
         @Override
         public void onError(String parentId) {
-            Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId);
+            Log.d(TAG, "Subscribe error for " + mComponentName + ": " + parentId);
             mCallback.onError();
             disconnect();
         }
 
         @Override
         public void onError(String parentId, Bundle options) {
-            Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId
+            Log.d(TAG, "Subscribe error for " + mComponentName + ": " + parentId
                     + ", options: " + options);
             mCallback.onError();
             disconnect();
@@ -149,7 +158,7 @@
          */
         @Override
         public void onConnectionFailed() {
-            Log.e(TAG, "Connection failed for " + mComponentName);
+            Log.d(TAG, "Connection failed for " + mComponentName);
             mCallback.onError();
             disconnect();
         }
@@ -167,11 +176,15 @@
     }
 
     /**
-     * Connects to the MediaBrowserService and starts playback. QSMediaBrowser.Callback#onError or
-     * QSMediaBrowser.Callback#onConnected will be called depending on whether it was successful.
-     * QSMediaBrowser#disconnect should be called after this to ensure the connection is closed.
+     * Connects to the MediaBrowserService and starts playback.
+     * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called
+     * depending on whether it was successful.
+     * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void restart() {
+        if (!mIsEnabled) {
+            return;
+        }
         disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
@@ -224,18 +237,21 @@
 
     /**
      * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser.
-     * QSMediaBrowser.Callback#onError or QSMediaBrowser.Callback#onConnected will be called
+     * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called
      * depending on whether it was successful.
-     * QSMediaBrowser#disconnect should be called after this to ensure the connection is closed.
+     * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void testConnection() {
+        if (!mIsEnabled) {
+            return;
+        }
         disconnect();
         final MediaBrowser.ConnectionCallback connectionCallback =
                 new MediaBrowser.ConnectionCallback() {
                     @Override
                     public void onConnected() {
                         Log.d(TAG, "connected");
-                        if (mMediaBrowser.getRoot() == null) {
+                        if (TextUtils.isEmpty(mMediaBrowser.getRoot())) {
                             mCallback.onError();
                         } else {
                             mCallback.onConnected();
@@ -264,7 +280,7 @@
     }
 
     /**
-     * Interface to handle results from QSMediaBrowser
+     * Interface to handle results from ResumeMediaBrowser
      */
     public static class Callback {
         /**
@@ -286,7 +302,7 @@
          * @param browser reference to the browser
          */
         public void addTrack(MediaDescription track, ComponentName component,
-                QSMediaBrowser browser) {
+                ResumeMediaBrowser browser) {
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 06821cd6..efc476d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -27,8 +27,10 @@
 import androidx.annotation.WorkerThread
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.LiveData
-
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.RepeatableExecutor
+import java.util.concurrent.Executor
+import javax.inject.Inject
 
 private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L
 
@@ -65,7 +67,7 @@
 }
 
 /** ViewModel for seek bar in QS media player. */
-class SeekBarViewModel(val bgExecutor: DelayableExecutor) {
+class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: RepeatableExecutor) {
 
     private var _data = Progress(false, false, null, null)
         set(value) {
@@ -89,17 +91,25 @@
     private var callback = object : MediaController.Callback() {
         override fun onPlaybackStateChanged(state: PlaybackState) {
             playbackState = state
-            if (shouldPollPlaybackPosition()) {
-                checkPlaybackPosition()
+            if (PlaybackState.STATE_NONE.equals(playbackState)) {
+                clearController()
+            } else {
+                checkIfPollingNeeded()
             }
         }
+
+        override fun onSessionDestroyed() {
+            clearController()
+        }
     }
+    private var cancel: Runnable? = null
 
     /** Listening state (QS open or closed) is used to control polling of progress. */
     var listening = true
-        set(value) {
-            if (value) {
-                checkPlaybackPosition()
+        set(value) = bgExecutor.execute {
+            if (field != value) {
+                field = value
+                checkIfPollingNeeded()
             }
         }
 
@@ -131,9 +141,7 @@
                 playbackState?.getState() == PlaybackState.STATE_NONE ||
                 (duration != null && duration <= 0)) false else true
         _data = Progress(enabled, seekAvailable, position, duration)
-        if (shouldPollPlaybackPosition()) {
-            checkPlaybackPosition()
-        }
+        checkIfPollingNeeded()
     }
 
     /**
@@ -145,6 +153,8 @@
     fun clearController() = bgExecutor.execute {
         controller = null
         playbackState = null
+        cancel?.run()
+        cancel = null
         _data = _data.copy(enabled = false)
     }
 
@@ -152,26 +162,34 @@
      * Call to clean up any resources.
      */
     @AnyThread
-    fun onDestroy() {
+    fun onDestroy() = bgExecutor.execute {
         controller = null
         playbackState = null
+        cancel?.run()
+        cancel = null
     }
 
-    @AnyThread
-    private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({
+    @WorkerThread
+    private fun checkPlaybackPosition() {
         val duration = _data.duration ?: -1
         val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt()
         if (currentPosition != null && _data.elapsedTime != currentPosition) {
             _data = _data.copy(elapsedTime = currentPosition)
         }
-        if (shouldPollPlaybackPosition()) {
-            checkPlaybackPosition()
-        }
-    }, POSITION_UPDATE_INTERVAL_MILLIS)
+    }
 
     @WorkerThread
-    private fun shouldPollPlaybackPosition(): Boolean {
-        return listening && playbackState?.isInMotion() ?: false
+    private fun checkIfPollingNeeded() {
+        val needed = listening && playbackState?.isInMotion() ?: false
+        if (needed) {
+            if (cancel == null) {
+                cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L,
+                        POSITION_UPDATE_INTERVAL_MILLIS)
+            }
+        } else {
+            cancel?.run()
+            cancel = null
+        }
     }
 
     /** Gets a listener to attach to the seek bar to handle seeking. */
@@ -188,7 +206,7 @@
 
     private class SeekBarChangeListener(
         val viewModel: SeekBarViewModel,
-        val bgExecutor: DelayableExecutor
+        val bgExecutor: Executor
     ) : SeekBar.OnSeekBarChangeListener {
         override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
             if (fromUser) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 7f7e108..03b1ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.pip;
 
 import android.animation.Animator;
+import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
 import android.content.Context;
@@ -26,6 +27,7 @@
 import android.view.animation.Interpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Interpolators;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -74,15 +76,12 @@
                 || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
     }
 
-    private final Interpolator mFastOutSlowInInterpolator;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
 
     private PipTransitionAnimator mCurrentAnimator;
 
     @Inject
     PipAnimationController(Context context, PipSurfaceTransactionHelper helper) {
-        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.fast_out_slow_in);
         mSurfaceTransactionHelper = helper;
     }
 
@@ -104,10 +103,11 @@
     }
 
     @SuppressWarnings("unchecked")
-    PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) {
+    PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
+            Rect sourceHintRect) {
         if (mCurrentAnimator == null) {
             mCurrentAnimator = setupPipTransitionAnimator(
-                    PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
+                    PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
                 && mCurrentAnimator.isRunning()) {
             // If we are still animating the fade into pip, then just move the surface and ensure
@@ -122,7 +122,7 @@
         } else {
             mCurrentAnimator.cancel();
             mCurrentAnimator = setupPipTransitionAnimator(
-                    PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
+                    PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
         }
         return mCurrentAnimator;
     }
@@ -133,7 +133,7 @@
 
     private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
-        animator.setInterpolator(mFastOutSlowInInterpolator);
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         animator.setFloatValues(FRACTION_START, FRACTION_END);
         return animator;
     }
@@ -331,6 +331,7 @@
                 @Override
                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
                     getSurfaceTransactionHelper()
+                            .resetScale(tx, leash, getDestinationBounds())
                             .crop(tx, leash, getDestinationBounds())
                             .round(tx, leash, shouldApplyCornerRadius());
                     tx.show(leash);
@@ -346,35 +347,46 @@
         }
 
         static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
-                Rect startValue, Rect endValue) {
+                Rect startValue, Rect endValue, Rect sourceHintRect) {
+            // Just for simplicity we'll interpolate between the source rect hint insets and empty
+            // insets to calculate the window crop
+            final Rect initialStartValue = new Rect(startValue);
+            final Rect sourceHintRectInsets = sourceHintRect != null
+                    ? new Rect(sourceHintRect.left - startValue.left,
+                            sourceHintRect.top - startValue.top,
+                            startValue.right - sourceHintRect.right,
+                            startValue.bottom - sourceHintRect.bottom)
+                    : null;
+            final Rect sourceInsets = new Rect(0, 0, 0, 0);
+
             // construct new Rect instances in case they are recycled
             return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
                     endValue, new Rect(startValue), new Rect(endValue)) {
-                private final Rect mTmpRect = new Rect();
-
-                private int getCastedFractionValue(float start, float end, float fraction) {
-                    return (int) (start * (1 - fraction) + end * fraction + .5f);
-                }
+                private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+                private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
 
                 @Override
                 void applySurfaceControlTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, float fraction) {
                     final Rect start = getStartValue();
                     final Rect end = getEndValue();
-                    mTmpRect.set(
-                            getCastedFractionValue(start.left, end.left, fraction),
-                            getCastedFractionValue(start.top, end.top, fraction),
-                            getCastedFractionValue(start.right, end.right, fraction),
-                            getCastedFractionValue(start.bottom, end.bottom, fraction));
-                    setCurrentValue(mTmpRect);
+                    Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
+                    setCurrentValue(bounds);
                     if (inScaleTransition()) {
                         if (isOutPipDirection(getTransitionDirection())) {
-                            getSurfaceTransactionHelper().scale(tx, leash, end, mTmpRect);
+                            getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
                         } else {
-                            getSurfaceTransactionHelper().scale(tx, leash, start, mTmpRect);
+                            getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
                         }
                     } else {
-                        getSurfaceTransactionHelper().crop(tx, leash, mTmpRect);
+                        if (sourceHintRectInsets != null) {
+                            Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
+                                    sourceHintRectInsets);
+                            getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue,
+                                    bounds, insets);
+                        } else {
+                            getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
+                        }
                     }
                     tx.apply();
                 }
@@ -390,11 +402,11 @@
 
                 @Override
                 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
-                    if (!inScaleTransition()) return;
                     // NOTE: intentionally does not apply the transaction here.
                     // this end transaction should get executed synchronously with the final
                     // WindowContainerTransaction in task organizer
-                    getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds())
+                    getSurfaceTransactionHelper()
+                            .resetScale(tx, leash, getDestinationBounds())
                             .crop(tx, leash, getDestinationBounds());
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 0d3a16e..2657694 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -296,6 +296,14 @@
      */
     public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, Rect outInsetBounds,
             int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
+        // Calculate the snap fraction of the current stack along the old movement bounds
+        final Rect postChangeStackBounds = new Rect(oldBounds);
+        final float snapFraction = getSnapFraction(postChangeStackBounds);
+
+        // Update the display layout, note that we have to do this on every rotation even if we
+        // aren't in PIP since we need to update the display layout to get the right resources
+        mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+
         // Bail early if the event is not sent to current {@link #mDisplayInfo}
         if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
             return false;
@@ -312,13 +320,6 @@
             return false;
         }
 
-        // Calculate the snap fraction of the current stack along the old movement bounds
-        final Rect postChangeStackBounds = new Rect(oldBounds);
-        final float snapFraction = getSnapFraction(postChangeStackBounds);
-
-        // Update the display layout
-        mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
-
         // Populate the new {@link #mDisplayInfo}.
         // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
         // therefore, the width/height may require a swap first.
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
index fc41d2e..65ea887 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
@@ -44,6 +44,7 @@
     private final float[] mTmpFloat9 = new float[9];
     private final RectF mTmpSourceRectF = new RectF();
     private final RectF mTmpDestinationRectF = new RectF();
+    private final Rect mTmpDestinationRect = new Rect();
 
     @Inject
     public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) {
@@ -90,7 +91,30 @@
         mTmpDestinationRectF.set(destinationBounds);
         mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
-                .setPosition(leash, destinationBounds.left, destinationBounds.top);
+                .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+        return this;
+    }
+
+    /**
+     * Operates the scale (setMatrix) on a given transaction and leash
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds, Rect insets) {
+        mTmpSourceRectF.set(sourceBounds);
+        mTmpDestinationRect.set(sourceBounds);
+        mTmpDestinationRect.inset(insets);
+        // Scale by the shortest edge and offset such that the top/left of the scaled inset source
+        // rect aligns with the top/left of the destination bounds
+        final float scale = sourceBounds.width() <= sourceBounds.height()
+                ? (float) destinationBounds.width() / sourceBounds.width()
+                : (float) destinationBounds.height() / sourceBounds.height();
+        final float left = destinationBounds.left - insets.left * scale;
+        final float top = destinationBounds.top - insets.top * scale;
+        mTmpTransform.setScale(scale, scale);
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+                .setWindowCrop(leash, mTmpDestinationRect)
+                .setPosition(leash, left, top);
         return this;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index c6f144a..42e0c56 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -143,8 +143,10 @@
             case MSG_RESIZE_ANIMATE: {
                 Rect currentBounds = (Rect) args.arg2;
                 Rect toBounds = (Rect) args.arg3;
+                Rect sourceHintRect = (Rect) args.arg4;
                 int duration = args.argi2;
-                animateResizePip(currentBounds, toBounds, args.argi1 /* direction */, duration);
+                animateResizePip(currentBounds, toBounds, sourceHintRect,
+                        args.argi1 /* direction */, duration);
                 if (updateBoundsCallback != null) {
                     updateBoundsCallback.accept(toBounds);
                 }
@@ -294,7 +296,8 @@
                 public void onTransactionReady(int id, SurfaceControl.Transaction t) {
                     t.apply();
                     scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
-                            direction, animationDurationMs, null /* updateBoundsCallback */);
+                            null /* sourceHintRect */, direction, animationDurationMs,
+                            null /* updateBoundsCallback */);
                     mInPip = false;
                 }
             });
@@ -357,7 +360,8 @@
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
 
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
-            scheduleAnimateResizePip(currentBounds, destinationBounds,
+            final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds);
+            scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect,
                     TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
                     null /* updateBoundsCallback */);
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
@@ -368,6 +372,21 @@
         }
     }
 
+    /**
+     * Returns the source hint rect if it is valid (if provided and is contained by the current
+     * task bounds).
+     */
+    private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) {
+        final Rect sourceHintRect = info.pictureInPictureParams != null
+                && info.pictureInPictureParams.hasSourceBoundsHint()
+                ? info.pictureInPictureParams.getSourceRectHint()
+                : null;
+        if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
+            return sourceHintRect;
+        }
+        return null;
+    }
+
     private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
         // If we are fading the PIP in, then we should move the pip to the final location as
         // soon as possible, but set the alpha immediately since the transaction can take a
@@ -552,13 +571,13 @@
             Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
             return;
         }
-        scheduleAnimateResizePip(mLastReportedBounds, toBounds,
+        scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */,
                 TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
     }
 
     private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
-            @PipAnimationController.TransitionDirection int direction, int durationMs,
-            Consumer<Rect> updateBoundsCallback) {
+            Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction,
+            int durationMs, Consumer<Rect> updateBoundsCallback) {
         if (!mInPip) {
             // can be initiated in other component, ignore if we are no longer in PIP
             return;
@@ -568,6 +587,7 @@
         args.arg1 = updateBoundsCallback;
         args.arg2 = currentBounds;
         args.arg3 = destinationBounds;
+        args.arg4 = sourceHintRect;
         args.argi1 = direction;
         args.argi2 = durationMs;
         mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
@@ -667,7 +687,8 @@
         }
         final Rect destinationBounds = new Rect(originalBounds);
         destinationBounds.offset(xOffset, yOffset);
-        animateResizePip(originalBounds, destinationBounds, TRANSITION_DIRECTION_SAME, durationMs);
+        animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */,
+                TRANSITION_DIRECTION_SAME, durationMs);
     }
 
     private void resizePip(Rect destinationBounds) {
@@ -745,7 +766,7 @@
         WindowOrganizer.applyTransaction(wct);
     }
 
-    private void animateResizePip(Rect currentBounds, Rect destinationBounds,
+    private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
             @PipAnimationController.TransitionDirection int direction, int durationMs) {
         if (Looper.myLooper() != mUpdateHandler.getLooper()) {
             throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
@@ -757,7 +778,7 @@
             return;
         }
         mPipAnimationController
-                .getAnimator(mLeash, currentBounds, destinationBounds)
+                .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect)
                 .setTransitionDirection(direction)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(durationMs)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 0d61449..2c76d70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -45,9 +45,6 @@
     }
 
     public void setNumPages(int numPages) {
-        if (numPages == getChildCount()) {
-            return;
-        }
         TypedArray array = getContext().obtainStyledAttributes(
                 new int[]{android.R.attr.colorControlActivated});
         int color = array.getColor(0, 0);
@@ -55,12 +52,12 @@
         setNumPages(numPages, color);
     }
 
-    /** Oveload of setNumPages that allows the indicator color to be specified.*/
+    /** Overload of setNumPages that allows the indicator color to be specified.*/
     public void setNumPages(int numPages, int color) {
+        setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
         if (numPages == getChildCount()) {
             return;
         }
-        setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
         if (mAnimating) {
             Log.w(TAG, "setNumPages during animation");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 191d475..94b4cee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -41,7 +41,6 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.Utils;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -82,8 +81,7 @@
             MediaHost mediaHost,
             UiEventLogger uiEventLogger
     ) {
-        super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost,
-                uiEventLogger);
+        super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger);
         if (mFooter != null) {
             removeView(mFooter.getView());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index b272b60..baa2dfd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,13 +64,16 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dumpable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipAnimationController;
 import com.android.systemui.pip.PipUI;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.stackdivider.Divider;
@@ -83,8 +86,6 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -101,8 +102,9 @@
  * Class to send information from overview to launcher with a binder.
  */
 @Singleton
-public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
-        NavigationModeController.ModeChangedListener, Dumpable {
+public class OverviewProxyService extends CurrentUserTracker implements
+        CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
+        Dumpable {
 
     private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
 
@@ -123,7 +125,6 @@
     private final NotificationShadeWindowController mStatusBarWinController;
     private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
     private final ComponentName mRecentsComponentName;
-    private final DeviceProvisionedController mDeviceProvisionedController;
     private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
     private final Intent mQuickStepIntent;
     private final ScreenshotHelper mScreenshotHelper;
@@ -383,8 +384,7 @@
         @Override
         public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
                 Insets visibleInsets, int taskId) {
-            mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
-                    taskId, SCREENSHOT_OVERVIEW, mHandler, null);
+            // Deprecated
         }
 
         @Override
@@ -434,6 +434,21 @@
             }
         }
 
+        @Override
+        public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
+                Insets visibleInsets, Task.TaskKey task) {
+            mScreenshotHelper.provideScreenshot(
+                    screenImageBundle,
+                    locationInScreen,
+                    visibleInsets,
+                    task.id,
+                    task.userId,
+                    task.sourceComponent,
+                    SCREENSHOT_OVERVIEW,
+                    mHandler,
+                    null);
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -480,7 +495,7 @@
                 return;
             }
 
-            mCurrentBoundedUserId = mDeviceProvisionedController.getCurrentUser();
+            mCurrentBoundedUserId = getCurrentUserId();
             mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
 
             Bundle params = new Bundle();
@@ -523,22 +538,6 @@
         }
     };
 
-    private final DeviceProvisionedListener mDeviceProvisionedCallback =
-                new DeviceProvisionedListener() {
-        @Override
-        public void onUserSetupChanged() {
-            if (mDeviceProvisionedController.isCurrentUserSetup()) {
-                internalConnectToCurrentUser();
-            }
-        }
-
-        @Override
-        public void onUserSwitched() {
-            mConnectionBackoffAttempts = 0;
-            internalConnectToCurrentUser();
-        }
-    };
-
     private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
 
     // This is the death handler for the binder from the launcher service
@@ -548,18 +547,18 @@
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyService(Context context, CommandQueue commandQueue,
-            DeviceProvisionedController provisionController,
             NavigationBarController navBarController, NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
             PipUI pipUI, Optional<Divider> dividerOptional,
-            Optional<Lazy<StatusBar>> statusBarOptionalLazy) {
+            Optional<Lazy<StatusBar>> statusBarOptionalLazy,
+            BroadcastDispatcher broadcastDispatcher) {
+        super(broadcastDispatcher);
         mContext = context;
         mPipUI = pipUI;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mHandler = new Handler();
         mNavBarController = navBarController;
         mStatusBarWinController = statusBarWinController;
-        mDeviceProvisionedController = provisionController;
         mConnectionBackoffAttempts = 0;
         mDividerOptional = dividerOptional;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
@@ -580,7 +579,7 @@
 
         // Listen for device provisioned/user setup
         updateEnabledState();
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
+        startTracking();
 
         // Listen for launcher package changes
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -604,6 +603,12 @@
         });
     }
 
+    @Override
+    public void onUserSwitched(int newUserId) {
+        mConnectionBackoffAttempts = 0;
+        internalConnectToCurrentUser();
+    }
+
     public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
             boolean gestureSwipeLeft) {
         try {
@@ -709,10 +714,8 @@
         disconnectFromLauncherService();
 
         // If user has not setup yet or already connected, do not try to connect
-        if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) {
-            Log.v(TAG_OPS, "Cannot attempt connection, is setup "
-                + mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled "
-                + isEnabled());
+        if (!isEnabled()) {
+            Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled());
             return;
         }
         mHandler.removeCallbacks(mConnectionRunnable);
@@ -722,7 +725,7 @@
             mBound = mContext.bindServiceAsUser(launcherServiceIntent,
                     mOverviewServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    UserHandle.of(mDeviceProvisionedController.getCurrentUser()));
+                    UserHandle.of(getCurrentUserId()));
         } catch (SecurityException e) {
             Log.e(TAG_OPS, "Unable to bind because of security error", e);
         }
@@ -881,8 +884,6 @@
         pw.println(TAG_OPS + " state:");
         pw.print("  recentsComponentName="); pw.println(mRecentsComponentName);
         pw.print("  isConnected="); pw.println(mOverviewProxy != null);
-        pw.print("  isCurrentUserSetup="); pw.println(mDeviceProvisionedController
-                .isCurrentUserSetup());
         pw.print("  connectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
 
         pw.print("  quickStepIntent="); pw.println(mQuickStepIntent);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 960c501..3e268f6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -225,8 +225,8 @@
                 res.getString(R.string.screenrecord_name));
 
         String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
-                ? res.getString(R.string.screenrecord_ongoing_screen_and_audio)
-                : res.getString(R.string.screenrecord_ongoing_screen_only);
+                ? res.getString(R.string.screenrecord_ongoing_screen_only)
+                : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
 
         mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID)
                 .setSmallIcon(R.drawable.ic_screenrecord)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
index 752f4fd..edbc3cf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
@@ -38,6 +38,7 @@
 public class ScreenInternalAudioRecorder {
     private static String TAG = "ScreenAudioRecorder";
     private static final int TIMEOUT = 500;
+    private static final float MIC_VOLUME_SCALE = 1.4f;
     private final Context mContext;
     private AudioRecord mAudioRecord;
     private AudioRecord mAudioRecordMic;
@@ -148,6 +149,10 @@
                     readShortsInternal = mAudioRecord.read(bufferInternal, 0,
                             bufferInternal.length);
                     readShortsMic = mAudioRecordMic.read(bufferMic, 0, bufferMic.length);
+
+                    // modify the volume
+                    bufferMic = scaleValues(bufferMic,
+                            readShortsMic, MIC_VOLUME_SCALE);
                     readBytes = Math.min(readShortsInternal, readShortsMic) * 2;
                     buffer = addAndConvertBuffers(bufferInternal, readShortsInternal, bufferMic,
                             readShortsMic);
@@ -168,6 +173,19 @@
         });
     }
 
+    private short[] scaleValues(short[] buff, int len, float scale) {
+        for (int i = 0; i < len; i++) {
+            int oldValue = buff[i];
+            int newValue = (int) (buff[i] * scale);
+            if (newValue > Short.MAX_VALUE) {
+                newValue = Short.MAX_VALUE;
+            } else if (newValue < Short.MIN_VALUE) {
+                newValue = Short.MIN_VALUE;
+            }
+            buff[i] = (short) (newValue);
+        }
+        return buff;
+    }
     private byte[] addAndConvertBuffers(short[] a1, int a1Limit, short[] a2, int a2Limit) {
         int size = Math.max(a1Limit, a2Limit);
         if (size < 0) return new byte[0];
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index c967648..8551c88 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -55,9 +55,9 @@
  */
 public class ScreenMediaRecorder {
     private static final int TOTAL_NUM_TRACKS = 1;
-    private static final int VIDEO_BIT_RATE = 10000000;
     private static final int VIDEO_FRAME_RATE = 30;
-    private static final int AUDIO_BIT_RATE = 16;
+    private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
+    private static final int AUDIO_BIT_RATE = 196000;
     private static final int AUDIO_SAMPLE_RATE = 44100;
     private static final int MAX_DURATION_MS = 60 * 60 * 1000;
     private static final long MAX_FILESIZE_BYTES = 5000000000L;
@@ -108,7 +108,7 @@
 
         // Set up audio source
         if (mAudioSource == MIC) {
-            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
         }
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
 
@@ -121,10 +121,13 @@
         wm.getDefaultDisplay().getRealMetrics(metrics);
         int screenWidth = metrics.widthPixels;
         int screenHeight = metrics.heightPixels;
+        int refereshRate = (int) wm.getDefaultDisplay().getRefreshRate();
+        int vidBitRate = screenHeight * screenWidth * refereshRate / VIDEO_FRAME_RATE
+                * VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO;
         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
         mMediaRecorder.setVideoSize(screenWidth, screenHeight);
-        mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE);
-        mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE);
+        mMediaRecorder.setVideoFrameRate(refereshRate);
+        mMediaRecorder.setVideoEncodingBitRate(vidBitRate);
         mMediaRecorder.setMaxDuration(MAX_DURATION_MS);
         mMediaRecorder.setMaxFileSize(MAX_FILESIZE_BYTES);
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index abd7e71..d057a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -24,12 +24,9 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.os.Bundle;
-import android.util.Log;
 import android.view.Gravity;
-import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
-import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.Spinner;
@@ -88,8 +85,8 @@
         });
 
         mModes = new ArrayList<>();
-        mModes.add(INTERNAL);
         mModes.add(MIC);
+        mModes.add(INTERNAL);
         mModes.add(MIC_AND_INTERNAL);
 
         mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java
index 2e0e746..3e78489 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java
@@ -88,12 +88,6 @@
         return layout;
     }
 
-    private void setDescription(LinearLayout layout, int description) {
-        if (description != Resources.ID_NULL) {
-            ((TextView) layout.getChildAt(1)).setText(description);
-        }
-    }
-
     @Override
     public View getDropDownView(int position, View convertView, ViewGroup parent) {
         switch (getItem(position)) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index a9d3772..9b1734d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -35,6 +35,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -494,8 +495,10 @@
     }
 
     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
-            Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) {
-        // TODO use taskId and visibleInsets
+            Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
+            Consumer<Uri> finisher, Runnable onComplete) {
+        // TODO: use task Id, userId, topComponent for smart handler
+        // TODO: use visibleInsets for animation
         mOnCompleteRunnable = onComplete;
         takeScreenshot(screenshot, finisher, screenshotScreenBounds);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
index 095c32f..0017b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -24,6 +24,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -234,8 +235,10 @@
     }
 
     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
-            Insets visibleInsets, int taskId, Consumer<Uri> finisher) {
-        // TODO use taskId and visibleInsets
+            Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
+            Consumer<Uri> finisher) {
+        // TODO: use task Id, userId, topComponent for smart handler
+        // TODO: use visibleInsets for animation
         takeScreenshot(screenshot, finisher, false, false, screenshotScreenBounds);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 98030d4..8322fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
 
 import android.app.Service;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
@@ -37,6 +38,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.systemui.shared.recents.utilities.BitmapUtil;
 
 import java.util.function.Consumer;
 
@@ -107,16 +109,19 @@
                     }
                     break;
                 case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
-                    Bitmap screenshot = screenshotRequest.getBitmap();
+                    Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(
+                            screenshotRequest.getBitmapBundle());
                     Rect screenBounds = screenshotRequest.getBoundsInScreen();
                     Insets insets = screenshotRequest.getInsets();
                     int taskId = screenshotRequest.getTaskId();
+                    int userId = screenshotRequest.getUserId();
+                    ComponentName topComponent = screenshotRequest.getTopComponent();
                     if (useCornerFlow) {
-                        mScreenshot.handleImageAsScreenshot(
-                                screenshot, screenBounds, insets, taskId, uriConsumer, onComplete);
+                        mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
+                                taskId, userId, topComponent, uriConsumer, onComplete);
                     } else {
-                        mScreenshotLegacy.handleImageAsScreenshot(
-                                screenshot, screenBounds, insets, taskId, uriConsumer);
+                        mScreenshotLegacy.handleImageAsScreenshot(screenshot, screenBounds, insets,
+                                taskId, userId, topComponent, uriConsumer);
                     }
                     break;
                 default:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 217148d..5628a24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -52,7 +52,6 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaDeviceManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -102,6 +101,12 @@
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
     }
+    private static final HashSet<Integer> INACTIVE_MEDIA_STATES = new HashSet<>();
+    static {
+        INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_NONE);
+        INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
+        INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
+    }
 
     private final NotificationEntryManager mEntryManager;
     private final MediaDataManager mMediaDataManager;
@@ -190,8 +195,7 @@
             KeyguardBypassController keyguardBypassController,
             @Main DelayableExecutor mainExecutor,
             DeviceConfigProxy deviceConfig,
-            MediaDataManager mediaDataManager,
-            MediaDeviceManager mediaDeviceManager) {
+            MediaDataManager mediaDataManager) {
         mContext = context;
         mMediaArtworkProcessor = mediaArtworkProcessor;
         mKeyguardBypassController = keyguardBypassController;
@@ -212,13 +216,11 @@
             @Override
             public void onPendingEntryAdded(NotificationEntry entry) {
                 mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
-                mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn());
             }
 
             @Override
             public void onPreEntryUpdated(NotificationEntry entry) {
                 mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
-                mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn());
             }
 
             @Override
@@ -239,7 +241,6 @@
                     int reason) {
                 onNotificationRemoved(entry.getKey());
                 mediaDataManager.onNotificationRemoved(entry.getKey());
-                mediaDeviceManager.onNotificationRemoved(entry.getKey());
             }
         });
 
@@ -252,10 +253,24 @@
                 mPropertiesChangedListener);
     }
 
+    /**
+     * Check if a state should be considered actively playing
+     * @param state a PlaybackState
+     * @return true if playing
+     */
     public static boolean isPlayingState(int state) {
         return !PAUSED_MEDIA_STATES.contains(state);
     }
 
+    /**
+     * Check if a state should be considered active (playing or paused)
+     * @param state a PlaybackState
+     * @return true if active
+     */
+    public static boolean isActiveState(int state) {
+        return !INACTIVE_MEDIA_STATES.contains(state);
+    }
+
     public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 3dda15b..a8c0324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -42,7 +42,6 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.util.Assert;
-import com.android.systemui.util.Utils;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -150,9 +149,7 @@
         final int N = activeNotifications.size();
         for (int i = 0; i < N; i++) {
             NotificationEntry ent = activeNotifications.get(i);
-            boolean hideMedia = Utils.useQsMediaPlayer(mContext);
             if (ent.isRowDismissed() || ent.isRowRemoved()
-                    || (ent.isMediaNotification() && hideMedia)
                     || mBubbleController.isBubbleNotificationSuppressedFromShade(ent)
                     || mFgsSectionController.hasEntry(ent)) {
                 // we don't want to update removed notifications because they could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index c988e12..84c8db3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -24,7 +24,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaDeviceManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
@@ -51,8 +50,6 @@
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
-import java.util.concurrent.Executor;
-
 import javax.inject.Singleton;
 
 import dagger.Lazy;
@@ -105,8 +102,7 @@
             KeyguardBypassController keyguardBypassController,
             @Main DelayableExecutor mainExecutor,
             DeviceConfigProxy deviceConfigProxy,
-            MediaDataManager mediaDataManager,
-            MediaDeviceManager mediaDeviceManager) {
+            MediaDataManager mediaDataManager) {
         return new NotificationMediaManager(
                 context,
                 statusBarLazy,
@@ -116,8 +112,7 @@
                 keyguardBypassController,
                 mainExecutor,
                 deviceConfigProxy,
-                mediaDataManager,
-                mediaDeviceManager);
+                mediaDataManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 3afd623..6335a09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification;
 
+import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
+
 import android.Manifest;
 import android.app.AppGlobals;
 import android.app.Notification;
@@ -27,6 +29,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -46,6 +49,7 @@
     private final NotificationGroupManager mGroupManager = Dependency.get(
             NotificationGroupManager.class);
     private final StatusBarStateController mStatusBarStateController;
+    private final Boolean mIsMediaFlagEnabled;
 
     private NotificationEntryManager.KeyguardEnvironment mEnvironment;
     private ShadeController mShadeController;
@@ -53,8 +57,11 @@
     private NotificationLockscreenUserManager mUserManager;
 
     @Inject
-    public NotificationFilter(StatusBarStateController statusBarStateController) {
+    public NotificationFilter(
+            StatusBarStateController statusBarStateController,
+            MediaFeatureFlag mediaFeatureFlag) {
         mStatusBarStateController = statusBarStateController;
+        mIsMediaFlagEnabled = mediaFeatureFlag.getEnabled();
     }
 
     private NotificationEntryManager.KeyguardEnvironment getEnvironment() {
@@ -133,6 +140,10 @@
                 }
             }
         }
+
+        if (mIsMediaFlagEnabled && isMediaNotification(sbn)) {
+            return true;
+        }
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
new file mode 100644
index 0000000..026a3ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
+
+import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+
+import javax.inject.Inject;
+
+/**
+ * Coordinates hiding (filtering) of media notifications.
+ */
+public class MediaCoordinator implements Coordinator {
+    private static final String TAG = "MediaCoordinator";
+
+    private final Boolean mIsMediaFeatureEnabled;
+
+    private final NotifFilter mMediaFilter = new NotifFilter(TAG) {
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            return mIsMediaFeatureEnabled && isMediaNotification(entry.getSbn());
+        }
+    };
+
+    @Inject
+    public MediaCoordinator(MediaFeatureFlag featureFlag) {
+        mIsMediaFeatureEnabled = featureFlag.getEnabled();
+    }
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+        pipeline.addFinalizeFilter(mMediaFilter);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 2b279bb..ac42964 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -57,7 +57,8 @@
             BubbleCoordinator bubbleCoordinator,
             HeadsUpCoordinator headsUpCoordinator,
             ConversationCoordinator conversationCoordinator,
-            PreparationCoordinator preparationCoordinator) {
+            PreparationCoordinator preparationCoordinator,
+            MediaCoordinator mediaCoordinator) {
         dumpManager.registerDumpable(TAG, this);
         mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
         mCoordinators.add(hideNotifsForOtherUsersCoordinator);
@@ -72,6 +73,7 @@
             mCoordinators.add(preparationCoordinator);
         }
         // TODO: add new Coordinators here! (b/112656837)
+        mCoordinators.add(mediaCoordinator);
 
         // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting)
         for (Coordinator c : mCoordinators) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 9925909..b0861bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -557,9 +557,9 @@
 
     private void focusExpandButtonIfNecessary() {
         if (mFocusOnVisibilityChange) {
-            NotificationHeaderView header = getVisibleNotificationHeader();
-            if (header != null) {
-                ImageView expandButton = header.getExpandButton();
+            NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
+            if (wrapper != null) {
+                View expandButton = wrapper.getExpandButton();
                 if (expandButton != null) {
                     expandButton.requestAccessibilityFocus();
                 }
@@ -1348,7 +1348,9 @@
         }
         ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
         View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
-        if (bubbleButton == null || actionContainer == null) {
+        LinearLayout actionContainerLayout =
+                layout.findViewById(com.android.internal.R.id.actions_container_layout);
+        if (bubbleButton == null || actionContainer == null || actionContainerLayout == null) {
             return;
         }
         boolean isPersonWithShortcut =
@@ -1374,8 +1376,16 @@
             bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
             bubbleButton.setVisibility(VISIBLE);
             actionContainer.setVisibility(VISIBLE);
+
+            int paddingEnd = getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.bubble_visible_padding_end);
+            actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0);
         } else  {
             bubbleButton.setVisibility(GONE);
+
+            int paddingEnd = getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.bubble_gone_padding_end);
+            actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index 84bc181..f1fe54a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -241,7 +241,6 @@
         } catch (PackageManager.NameNotFoundException e) {
             mPkgIcon = mPm.getDefaultActivityIcon();
         }
-        ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
     }
 
     private void bindDelegate() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index 15499b8..fe70c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -50,6 +50,7 @@
     private lateinit var conversationBadgeBg: View
     private lateinit var expandButton: View
     private lateinit var expandButtonContainer: View
+    private lateinit var expandButtonInnerContainer: View
     private lateinit var imageMessageContainer: ViewGroup
     private lateinit var messagingLinearLayout: MessagingLinearLayout
     private lateinit var conversationTitleView: View
@@ -69,6 +70,8 @@
             expandButton = requireViewById(com.android.internal.R.id.expand_button)
             expandButtonContainer =
                     requireViewById(com.android.internal.R.id.expand_button_container)
+            expandButtonInnerContainer =
+                    requireViewById(com.android.internal.R.id.expand_button_inner_container)
             importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
             appName = requireViewById(com.android.internal.R.id.app_name_text)
             conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
@@ -134,6 +137,8 @@
         )
     }
 
+    override fun getExpandButton() = expandButtonInnerContainer
+
     override fun setShelfIconVisible(visible: Boolean) {
         if (conversationLayout.isImportantConversation) {
             if (conversationIconView.visibility != GONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index f8b7831..4c9cb20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -317,6 +317,11 @@
     }
 
     @Override
+    public View getExpandButton() {
+        return mExpandButton;
+    }
+
+    @Override
     public int getOriginalIconColor() {
         return mIcon.getOriginalIconColor();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 02e537d..30080e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -240,6 +240,13 @@
         return null;
     }
 
+    /**
+     * @return the expand button if it exists
+     */
+    public @Nullable View getExpandButton() {
+        return null;
+    }
+
     public int getOriginalIconColor() {
         return Notification.COLOR_INVALID;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 46c873d..4337e20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseRelativeLayout;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 
+import java.io.PrintWriter;
 import java.util.Objects;
 
 public class NavigationBarInflaterView extends FrameLayout
@@ -469,4 +470,10 @@
     private static float convertDpToPx(Context context, float dp) {
         return dp * context.getResources().getDisplayMetrics().density;
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println("NavigationBarInflaterView {");
+        pw.println("      mCurrentLayout: " + mCurrentLayout);
+        pw.println("    }");
+    }
 }
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 2978772..6b37ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -1198,6 +1198,9 @@
 
         pw.println("    }");
 
+        if (mNavigationInflaterView != null) {
+            mNavigationInflaterView.dump(pw);
+        }
         mContextualButtonGroup.dump(pw);
         mRecentsOnboarding.dump(pw);
         mRegionSamplingHelper.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index f58cce5..76c51d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -572,6 +572,9 @@
                     .setInterpolator(Interpolators.LINEAR)
                     .setDuration(AOD_ICONS_APPEAR_DURATION)
                     .start();
+        } else {
+            mAodIcons.setAlpha(1.0f);
+            mAodIcons.setTranslationY(0);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index b4de3cd..18a7add 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -640,8 +640,7 @@
                         + " dataState=" + state.getDataRegistrationState());
             }
             mServiceState = state;
-            // onDisplayInfoChanged is invoked directly after onServiceStateChanged, so not calling
-            // updateTelephony() to prevent icon flickering in case of overrides.
+            updateTelephony();
         }
 
         @Override
@@ -651,12 +650,6 @@
                         + " type=" + networkType);
             }
             mDataState = state;
-            if (networkType != mTelephonyDisplayInfo.getNetworkType()) {
-                Log.d(mTag, "onDataConnectionStateChanged:"
-                        + " network type change and reset displayInfo. type=" + networkType);
-                mTelephonyDisplayInfo = new TelephonyDisplayInfo(networkType,
-                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
-            }
             updateTelephony();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
index 6c3538c..a31ea7c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
@@ -40,7 +40,7 @@
 
         setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
 
-        mIconView.setImageDrawable(res.getDrawable(R.drawable.dismiss_target_x));
+        mIconView.setImageDrawable(res.getDrawable(R.drawable.ic_close_white));
         addView(mIconView);
 
         setViewSizes();
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index b1792d0..5c9db54 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -133,4 +133,13 @@
                 Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
         return flag > 0;
     }
+
+    /**
+     * Allow media resumption controls. Requires {@link #useQsMediaPlayer(Context)} to be enabled.
+     * Off by default, but can be enabled by setting to 1
+     */
+    public static boolean useMediaResumption(Context context) {
+        int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_resumption", 0);
+        return useQsMediaPlayer(context) && flag > 0;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
index f468192..2aed75e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -29,9 +29,9 @@
 class BubblePersistentRepositoryTest : SysuiTestCase() {
 
     private val bubbles = listOf(
-            BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1"),
-            BubbleEntity(10, "com.example.chat", "alice and bob", "key-2"),
-            BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3")
+            BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0),
+            BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428),
+            BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0)
     )
     private lateinit var repository: BubblePersistentRepository
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 2bb6bb8..f9d611c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -37,9 +37,9 @@
     private val user0 = UserHandle.of(0)
     private val user10 = UserHandle.of(10)
 
-    private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1")
-    private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2")
-    private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3")
+    private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0)
+    private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428)
+    private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0)
 
     private val bubbles = listOf(bubble1, bubble2, bubble3)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
index 79701ec..4946787 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
@@ -31,17 +31,17 @@
 class BubbleXmlHelperTest : SysuiTestCase() {
 
     private val bubbles = listOf(
-        BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1"),
-        BubbleEntity(10, "com.example.chat", "alice and bob", "k2"),
-        BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3")
+        BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0),
+        BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428),
+        BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0)
     )
 
     @Test
     fun testWriteXml() {
         val expectedEntries = """
-            <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" />
-            <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" />
-            <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" />
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" />
+            <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" />
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" />
         """.trimIndent()
         ByteArrayOutputStream().use {
             writeXml(it, bubbles)
@@ -56,9 +56,9 @@
         val src = """
             <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
             <bs>
-            <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" />
-            <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" />
-            <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" />
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" />
+            <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" />
+            <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" />
             </bs>
         """.trimIndent()
         val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 9d2b6f4..737ced6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -31,6 +31,7 @@
 import android.widget.ImageView
 import android.widget.SeekBar
 import android.widget.TextView
+import androidx.lifecycle.LiveData
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -41,6 +42,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -48,6 +50,7 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "TEST_KEY"
 private const val APP = "APP"
@@ -67,13 +70,14 @@
 
     private lateinit var player: MediaControlPanel
 
-    private lateinit var fgExecutor: FakeExecutor
     private lateinit var bgExecutor: FakeExecutor
     @Mock private lateinit var activityStarter: ActivityStarter
 
     @Mock private lateinit var holder: PlayerViewHolder
     @Mock private lateinit var view: TransitionLayout
     @Mock private lateinit var mediaHostStatesManager: MediaHostStatesManager
+    @Mock private lateinit var seekBarViewModel: SeekBarViewModel
+    @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
     private lateinit var appIcon: ImageView
     private lateinit var appName: TextView
     private lateinit var albumView: ImageView
@@ -95,20 +99,17 @@
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
     private val disabledDevice = MediaDeviceData(false, null, null)
 
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
     @Before
     fun setUp() {
-        fgExecutor = FakeExecutor(FakeSystemClock())
         bgExecutor = FakeExecutor(FakeSystemClock())
 
-        activityStarter = mock(ActivityStarter::class.java)
-        mediaHostStatesManager = mock(MediaHostStatesManager::class.java)
-
-        player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter,
-                mediaHostStatesManager)
+        player = MediaControlPanel(context, bgExecutor, activityStarter, mediaHostStatesManager,
+                seekBarViewModel)
+        whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
-        holder = mock(PlayerViewHolder::class.java)
-        view = mock(TransitionLayout::class.java)
         whenever(holder.player).thenReturn(view)
         appIcon = ImageView(context)
         whenever(holder.appIcon).thenReturn(appIcon)
@@ -171,7 +172,7 @@
     @Test
     fun bindWhenUnattached() {
         val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, null, null, device)
+                emptyList(), PACKAGE, null, null, device, null)
         player.bind(state)
         assertThat(player.isPlaying()).isFalse()
     }
@@ -180,7 +181,7 @@
     fun bindText() {
         player.attach(holder)
         val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device)
+                emptyList(), PACKAGE, session.getSessionToken(), null, device, null)
         player.bind(state)
         assertThat(appName.getText()).isEqualTo(APP)
         assertThat(titleText.getText()).isEqualTo(TITLE)
@@ -191,7 +192,7 @@
     fun bindBackgroundColor() {
         player.attach(holder)
         val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device)
+                emptyList(), PACKAGE, session.getSessionToken(), null, device, null)
         player.bind(state)
         val list = ArgumentCaptor.forClass(ColorStateList::class.java)
         verify(view).setBackgroundTintList(list.capture())
@@ -202,7 +203,7 @@
     fun bindDevice() {
         player.attach(holder)
         val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device)
+                emptyList(), PACKAGE, session.getSessionToken(), null, device, null)
         player.bind(state)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isTrue()
@@ -212,7 +213,7 @@
     fun bindDisabledDevice() {
         player.attach(holder)
         val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice)
+                emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, null)
         player.bind(state)
         assertThat(seamless.isEnabled()).isFalse()
         assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
@@ -223,7 +224,7 @@
     fun bindNullDevice() {
         player.attach(holder)
         val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null)
+                emptyList(), PACKAGE, session.getSessionToken(), null, null, null)
         player.bind(state)
         assertThat(seamless.isEnabled()).isTrue()
         assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 48e3b0a..bed5c9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -79,16 +79,16 @@
         mManager.addListener(mListener);
 
         mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, KEY);
+                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, null, KEY, false);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
 
     @Test
     public void eventNotEmittedWithoutDevice() {
         // WHEN data source emits an event without device data
-        mDataListener.onMediaDataLoaded(KEY, mMediaData);
+        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN an event isn't emitted
-        verify(mListener, never()).onMediaDataLoaded(eq(KEY), any());
+        verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
 
     @Test
@@ -96,7 +96,7 @@
         // WHEN device source emits an event without media data
         mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
         // THEN an event isn't emitted
-        verify(mListener, never()).onMediaDataLoaded(eq(KEY), any());
+        verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
 
     @Test
@@ -104,22 +104,22 @@
         // GIVEN that a device event has already been received
         mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
         // WHEN media event is received
-        mDataListener.onMediaDataLoaded(KEY, mMediaData);
+        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
-        verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture());
+        verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
     @Test
     public void emitEventAfterMediaFirst() {
         // GIVEN that media event has already been received
-        mDataListener.onMediaDataLoaded(KEY, mMediaData);
+        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
         // WHEN device event is received
         mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
-        verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture());
+        verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -133,7 +133,7 @@
 
     @Test
     public void mediaDataRemovedAfterMediaEvent() {
-        mDataListener.onMediaDataLoaded(KEY, mMediaData);
+        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
         mDataListener.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
@@ -145,6 +145,18 @@
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
 
+    @Test
+    public void mediaDataKeyUpdated() {
+        // GIVEN that device and media events have already been received
+        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+        // WHEN the key is changed
+        mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
+        // THEN the listener gets a load event with the correct keys
+        ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+        verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture());
+    }
+
     private MediaDataManager.Listener captureDataListener() {
         ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass(
                 MediaDataManager.Listener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index c0aef8a..3a3140f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -23,8 +23,6 @@
 import android.media.RoutingSessionInfo
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
-import android.os.Process
-import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -67,6 +65,7 @@
 public class MediaDeviceManagerTest : SysuiTestCase() {
 
     private lateinit var manager: MediaDeviceManager
+    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
     @Mock private lateinit var lmm: LocalMediaManager
     @Mock private lateinit var mr2: MediaRouter2Manager
@@ -80,13 +79,14 @@
     private lateinit var metadataBuilder: MediaMetadata.Builder
     private lateinit var playbackBuilder: PlaybackState.Builder
     private lateinit var notifBuilder: Notification.Builder
-    private lateinit var sbn: StatusBarNotification
+    private lateinit var mediaData: MediaData
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
     @Before
     fun setUp() {
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor)
+        manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor,
+            mediaDataManager)
         manager.addListener(listener)
 
         // Configure mocks.
@@ -117,8 +117,8 @@
             setSmallIcon(android.R.drawable.ic_media_pause)
             setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
         }
-        sbn = StatusBarNotification(PACKAGE, PACKAGE, 0, "TAG", Process.myUid(), 0, 0,
-                notifBuilder.build(), Process.myUserHandle(), 0)
+        mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+            emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null)
     }
 
     @After
@@ -128,33 +128,33 @@
 
     @Test
     fun removeUnknown() {
-        manager.onNotificationRemoved("unknown")
+        manager.onMediaDataRemoved("unknown")
     }
 
     @Test
     fun addNotification() {
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         verify(lmmFactory).create(PACKAGE)
     }
 
     @Test
     fun featureDisabled() {
         whenever(featureFlag.enabled).thenReturn(false)
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         verify(lmmFactory, never()).create(PACKAGE)
     }
 
     @Test
     fun addAndRemoveNotification() {
-        manager.onNotificationAdded(KEY, sbn)
-        manager.onNotificationRemoved(KEY)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        manager.onMediaDataRemoved(KEY)
         verify(lmm).unregisterCallback(any())
     }
 
     @Test
     fun deviceEventOnAddNotification() {
         // WHEN a notification is added
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         val deviceCallback = captureCallback()
         // THEN the update is dispatched to the listener
         val data = captureDeviceData(KEY)
@@ -165,7 +165,7 @@
 
     @Test
     fun deviceListUpdate() {
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         val deviceCallback = captureCallback()
         // WHEN the device list changes
         deviceCallback.onDeviceListUpdate(mutableListOf(device))
@@ -179,7 +179,7 @@
 
     @Test
     fun selectedDeviceStateChanged() {
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         val deviceCallback = captureCallback()
         // WHEN the selected device changes state
         deviceCallback.onSelectedDeviceStateChanged(device, 1)
@@ -193,9 +193,9 @@
 
     @Test
     fun listenerReceivesKeyRemoved() {
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         // WHEN the notification is removed
-        manager.onNotificationRemoved(KEY)
+        manager.onMediaDataRemoved(KEY)
         // THEN the listener receives key removed event
         verify(listener).onKeyRemoved(eq(KEY))
     }
@@ -205,7 +205,7 @@
         // GIVEN that MR2Manager returns null for routing session
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
         // WHEN a notification is added
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         // THEN the device is disabled
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
@@ -216,7 +216,7 @@
     @Test
     fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceChanged() {
         // GIVEN a notif is added
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         reset(listener)
         // AND MR2Manager returns null for routing session
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
@@ -234,7 +234,7 @@
     @Test
     fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceListUpdate() {
         // GIVEN a notif is added
-        manager.onNotificationAdded(KEY, sbn)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
         reset(listener)
         // GIVEN that MR2Manager returns null for routing session
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index c21343c..7d44327 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.media
 
+import android.media.MediaMetadata
 import android.media.session.MediaController
+import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
@@ -41,6 +43,10 @@
 import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "KEY"
+private const val PACKAGE = "PKG"
+private const val SESSION_KEY = "SESSION_KEY"
+private const val SESSION_ARTIST = "SESSION_ARTIST"
+private const val SESSION_TITLE = "SESSION_TITLE"
 
 private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
 private fun <T> anyObject(): T {
@@ -54,12 +60,15 @@
     @Mock private lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock private lateinit var mediaController: MediaController
     @Mock private lateinit var executor: DelayableExecutor
-    @Mock private lateinit var mediaData: MediaData
     @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
     @Mock private lateinit var cancellationRunnable: Runnable
     @Captor private lateinit var timeoutCaptor: ArgumentCaptor<Runnable>
     @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private lateinit var metadataBuilder: MediaMetadata.Builder
+    private lateinit var playbackBuilder: PlaybackState.Builder
+    private lateinit var session: MediaSession
+    private lateinit var mediaData: MediaData
     private lateinit var mediaTimeoutListener: MediaTimeoutListener
 
     @Before
@@ -68,22 +77,39 @@
         `when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable)
         mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor)
         mediaTimeoutListener.timeoutCallback = timeoutCallback
+
+        // Create a media session and notification for testing.
+        metadataBuilder = MediaMetadata.Builder().apply {
+            putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+            putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+        }
+        playbackBuilder = PlaybackState.Builder().apply {
+            setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
+            setActions(PlaybackState.ACTION_PLAY)
+        }
+        session = MediaSession(context, SESSION_KEY).apply {
+            setMetadata(metadataBuilder.build())
+            setPlaybackState(playbackBuilder.build())
+        }
+        session.setActive(true)
+        mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+            emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null)
     }
 
     @Test
     fun testOnMediaDataLoaded_registersPlaybackListener() {
-        mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
 
         // Ignores is same key
         clearInvocations(mediaController)
-        mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
+        mediaTimeoutListener.onMediaDataLoaded(KEY, KEY, mediaData)
         verify(mediaController, never()).registerCallback(anyObject())
     }
 
     @Test
     fun testOnMediaDataRemoved_unregistersPlaybackListener() {
-        mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         mediaTimeoutListener.onMediaDataRemoved(KEY)
         verify(mediaController).unregisterCallback(anyObject())
 
@@ -105,7 +131,7 @@
 
     @Test
     fun testOnPlaybackStateChanged_cancelsTimeout_whenResumed() {
-        // Assuming we're have a pending timeout
+        // Assuming we have a pending timeout
         testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
 
         mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
@@ -114,6 +140,17 @@
     }
 
     @Test
+    fun testOnPlaybackStateChanged_reusesTimeout_whenNotPlaying() {
+        // Assuming we have a pending timeout
+        testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
+
+        clearInvocations(cancellationRunnable)
+        mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
+                .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build())
+        verify(cancellationRunnable, never()).run()
+    }
+
+    @Test
     fun testTimeoutCallback_invokedIfTimeout() {
         // Assuming we're have a pending timeout
         testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
@@ -124,7 +161,7 @@
 
     @Test
     fun testIsTimedOut() {
-        mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         assertThat(mediaTimeoutListener.isTimedOut(KEY)).isFalse()
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 19e15b3..24e9bd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -29,6 +29,7 @@
 
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.FakeRepeatableExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 
@@ -71,7 +72,7 @@
     @Before
     fun setUp() {
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        viewModel = SeekBarViewModel(fakeExecutor)
+        viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor))
         mockController = mock(MediaController::class.java)
         whenever(mockController.sessionToken).thenReturn(token1)
         mockTransport = mock(MediaController.TransportControls::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index b7a2633..536cae4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -82,7 +82,7 @@
     @Test
     public void getAnimator_withBounds_returnBoundsAnimator() {
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, new Rect(), new Rect());
+                .getAnimator(mLeash, new Rect(), new Rect(), null);
 
         assertEquals("Expect ANIM_TYPE_BOUNDS animation",
                 animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -94,12 +94,12 @@
         final Rect endValue1 = new Rect(100, 100, 200, 200);
         final Rect endValue2 = new Rect(200, 200, 300, 300);
         final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
-                .getAnimator(mLeash, startValue, endValue1);
+                .getAnimator(mLeash, startValue, endValue1, null);
         oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
         oldAnimator.start();
 
         final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
-                .getAnimator(mLeash, startValue, endValue2);
+                .getAnimator(mLeash, startValue, endValue2, null);
 
         assertEquals("getAnimator with same type returns same animator",
                 oldAnimator, newAnimator);
@@ -129,7 +129,7 @@
         final Rect endValue1 = new Rect(100, 100, 200, 200);
         final Rect endValue2 = new Rect(200, 200, 300, 300);
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, startValue, endValue1);
+                .getAnimator(mLeash, startValue, endValue1, null);
 
         animator.updateEndValue(endValue2);
 
@@ -141,7 +141,7 @@
         final Rect startValue = new Rect(0, 0, 100, 100);
         final Rect endValue = new Rect(100, 100, 200, 200);
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, startValue, endValue);
+                .getAnimator(mLeash, startValue, endValue, null);
         animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
 
         animator.setPipAnimationCallback(mPipAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 595ba89..5a81d36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -27,8 +27,10 @@
 
 import android.Manifest;
 import android.app.Notification;
+import android.app.Notification.MediaStyle;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.media.session.MediaSession;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
@@ -40,6 +42,7 @@
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
@@ -51,6 +54,7 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,10 +77,16 @@
     ForegroundServiceController mFsc;
     @Mock
     KeyguardEnvironment mEnvironment;
+    @Mock
+    MediaFeatureFlag mMediaFeatureFlag;
+    @Mock
+    StatusBarStateController mStatusBarStateController;
     private final IPackageManager mMockPackageManager = mock(IPackageManager.class);
 
     private NotificationFilter mNotificationFilter;
     private ExpandableNotificationRow mRow;
+    private NotificationEntry mMediaEntry;
+    private MediaSession mMediaSession;
 
     @Before
     public void setUp() throws Exception {
@@ -84,6 +94,12 @@
         MockitoAnnotations.initMocks(this);
         when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
 
+        mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION");
+        NotificationEntryBuilder builder = new NotificationEntryBuilder();
+        builder.modifyNotification(mContext).setStyle(
+                new MediaStyle().setMediaSession(mMediaSession.getSessionToken()));
+        mMediaEntry = builder.build();
+
         when(mMockPackageManager.checkUidPermission(
                 eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
                 eq(UID_NORMAL)))
@@ -107,7 +123,12 @@
                 mDependency,
                 TestableLooper.get(this));
         mRow = testHelper.createRow();
-        mNotificationFilter = new NotificationFilter(mock(StatusBarStateController.class));
+        mNotificationFilter = new NotificationFilter(mStatusBarStateController, mMediaFeatureFlag);
+    }
+
+    @After
+    public void tearDown() {
+        mMediaSession.release();
     }
 
     @Test
@@ -218,6 +239,56 @@
         assertFalse(mNotificationFilter.shouldFilterOut(entry));
     }
 
+    @Test
+    public void shouldFilterOtherNotificationWhenDisabled() {
+        // GIVEN that the media feature is disabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
+        NotificationFilter filter = new NotificationFilter(mStatusBarStateController,
+                mMediaFeatureFlag);
+        // WHEN the media filter is asked about an entry
+        NotificationEntry otherEntry = new NotificationEntryBuilder().build();
+        final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
+        // THEN it shouldn't be filtered
+        assertFalse(shouldFilter);
+    }
+
+    @Test
+    public void shouldFilterOtherNotificationWhenEnabled() {
+        // GIVEN that the media feature is enabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
+        NotificationFilter filter = new NotificationFilter(mStatusBarStateController,
+                mMediaFeatureFlag);
+        // WHEN the media filter is asked about an entry
+        NotificationEntry otherEntry = new NotificationEntryBuilder().build();
+        final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
+        // THEN it shouldn't be filtered
+        assertFalse(shouldFilter);
+    }
+
+    @Test
+    public void shouldFilterMediaNotificationWhenDisabled() {
+        // GIVEN that the media feature is disabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
+        NotificationFilter filter = new NotificationFilter(mStatusBarStateController,
+                mMediaFeatureFlag);
+        // WHEN the media filter is asked about a media entry
+        final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
+        // THEN it shouldn't be filtered
+        assertFalse(shouldFilter);
+    }
+
+    @Test
+    public void shouldFilterMediaNotificationWhenEnabled() {
+        // GIVEN that the media feature is enabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
+        NotificationFilter filter = new NotificationFilter(mStatusBarStateController,
+                mMediaFeatureFlag);
+        // WHEN the media filter is asked about a media entry
+        final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
+        // THEN it should be filtered
+        assertTrue(shouldFilter);
+    }
+
     private void initStatusBarNotification(boolean allowDuringSetup) {
         Bundle bundle = new Bundle();
         bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
new file mode 100644
index 0000000..c5dc2b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification.MediaStyle;
+import android.media.session.MediaSession;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public final class MediaCoordinatorTest extends SysuiTestCase {
+
+    private MediaSession mMediaSession;
+    private NotificationEntry mOtherEntry;
+    private NotificationEntry mMediaEntry;
+
+    @Mock private NotifPipeline mNotifPipeline;
+    @Mock private MediaFeatureFlag mMediaFeatureFlag;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mOtherEntry = new NotificationEntryBuilder().build();
+        mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION");
+        NotificationEntryBuilder builder = new NotificationEntryBuilder();
+        builder.modifyNotification(mContext).setStyle(
+                new MediaStyle().setMediaSession(mMediaSession.getSessionToken()));
+        mMediaEntry = builder.build();
+    }
+
+    @After
+    public void tearDown() {
+        mMediaSession.release();
+    }
+
+    @Test
+    public void shouldFilterOtherNotificationWhenDisabled() {
+        // GIVEN that the media feature is disabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
+        MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag);
+        // WHEN the media filter is asked about an entry
+        NotifFilter filter = captureFilter(coordinator);
+        final boolean shouldFilter = filter.shouldFilterOut(mOtherEntry, 0);
+        // THEN it shouldn't be filtered
+        assertThat(shouldFilter).isFalse();
+    }
+
+    @Test
+    public void shouldFilterOtherNotificationWhenEnabled() {
+        // GIVEN that the media feature is enabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
+        MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag);
+        // WHEN the media filter is asked about an entry
+        NotifFilter filter = captureFilter(coordinator);
+        final boolean shouldFilter = filter.shouldFilterOut(mOtherEntry, 0);
+        // THEN it shouldn't be filtered
+        assertThat(shouldFilter).isFalse();
+    }
+
+    @Test
+    public void shouldFilterMediaNotificationWhenDisabled() {
+        // GIVEN that the media feature is disabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
+        MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag);
+        // WHEN the media filter is asked about a media entry
+        NotifFilter filter = captureFilter(coordinator);
+        final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry, 0);
+        // THEN it shouldn't be filtered
+        assertThat(shouldFilter).isFalse();
+    }
+
+    @Test
+    public void shouldFilterMediaNotificationWhenEnabled() {
+        // GIVEN that the media feature is enabled
+        when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
+        MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag);
+        // WHEN the media filter is asked about a media entry
+        NotifFilter filter = captureFilter(coordinator);
+        final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry, 0);
+        // THEN it should be filtered
+        assertThat(shouldFilter).isTrue();
+    }
+
+    private NotifFilter captureFilter(MediaCoordinator coordinator) {
+        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+        coordinator.attach(mNotifPipeline);
+        verify(mNotifPipeline).addFinalizeFilter(filterCaptor.capture());
+        return filterCaptor.getValue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index b018b59..ed4f8b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -30,11 +29,13 @@
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -98,4 +99,42 @@
         verify(mockExpanded, times(1)).setVisibility(View.VISIBLE);
         verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE);
     }
+
+    @Test
+    @UiThreadTest
+    public void testExpandButtonFocusIsCalled() {
+        View mockContractedEB = mock(NotificationExpandButton.class);
+        View mockContracted = mock(NotificationHeaderView.class);
+        when(mockContracted.animate()).thenReturn(mock(ViewPropertyAnimator.class));
+        when(mockContracted.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
+                mockContractedEB);
+
+        View mockExpandedEB = mock(NotificationExpandButton.class);
+        View mockExpanded = mock(NotificationHeaderView.class);
+        when(mockExpanded.animate()).thenReturn(mock(ViewPropertyAnimator.class));
+        when(mockExpanded.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
+                mockExpandedEB);
+
+        View mockHeadsUpEB = mock(NotificationExpandButton.class);
+        View mockHeadsUp = mock(NotificationHeaderView.class);
+        when(mockHeadsUp.animate()).thenReturn(mock(ViewPropertyAnimator.class));
+        when(mockHeadsUp.findViewById(com.android.internal.R.id.expand_button)).thenReturn(
+                mockHeadsUpEB);
+
+        // Set up all 3 child forms
+        mView.setContractedChild(mockContracted);
+        mView.setExpandedChild(mockExpanded);
+        mView.setHeadsUpChild(mockHeadsUp);
+
+        // This is required to call requestAccessibilityFocus()
+        mView.setFocusOnVisibilityChange();
+
+        // The following will initialize the view and switch from not visible to expanded.
+        // (heads-up is actually an alternate form of contracted, hence this enters expanded state)
+        mView.setHeadsUp(true);
+
+        verify(mockContractedEB, times(0)).requestAccessibilityFocus();
+        verify(mockExpandedEB, times(1)).requestAccessibilityFocus();
+        verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index e56ef5b..f327967 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -161,25 +161,6 @@
     }
 
     @Test
-    public void testBindNotification_SetsTextApplicationName() throws Exception {
-        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
-        mInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mChannelEditorDialogController,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mEntry,
-                null,
-                true,
-                false);
-        final TextView textView = mInfo.findViewById(R.id.pkg_name);
-        assertTrue(textView.getText().toString().contains("App Name"));
-        assertEquals(VISIBLE, mInfo.findViewById(R.id.header).getVisibility());
-    }
-
-    @Test
     public void testBindNotification_SetsName() {
         mInfo.bindNotification(
                 mMockPackageManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index be43e19..177e845 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
@@ -56,6 +57,8 @@
     @Mock
     NotificationMediaManager mNotificationMediaManager;
     @Mock
+    NotificationIconContainer mNotificationIconContainer;
+    @Mock
     DozeParameters mDozeParameters;
     @Mock
     NotificationShadeWindowView mNotificationShadeWindowView;
@@ -67,7 +70,7 @@
 
         when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mNotificationShadeWindowView);
         when(mNotificationShadeWindowView.findViewById(anyInt())).thenReturn(
-                        mock(NotificationIconContainer.class));
+                mNotificationIconContainer);
 
         mController = new NotificationIconAreaController(mContext, mStatusBar,
                 mStatusBarStateController, mWakeUpCoordinator, mKeyguardBypassController,
@@ -87,4 +90,12 @@
 
         assertTrue(mController.shouldShouldLowPriorityIcons());
     }
+
+    @Test
+    public void testAppearResetsTranslation() {
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
+        mController.appearAodIcons();
+        verify(mNotificationIconContainer).setTranslationY(0);
+        verify(mNotificationIconContainer).setAlpha(1.0f);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
new file mode 100644
index 0000000..477f615
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+/**
+ * A fake to use in tests.
+ */
+public class FakeRepeatableExecutor extends RepeatableExecutorImpl {
+
+    /**
+     * Initializes a fake RepeatableExecutor from a fake executor.
+     *
+     * Use the fake executor to actually process tasks.
+     *
+     * @param executor fake executor.
+     */
+    public FakeRepeatableExecutor(FakeExecutor executor) {
+        super(executor);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 468e93a..d15c60b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -725,7 +725,8 @@
                 case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
                 case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
                 case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:
-                case WindowManager.LayoutParams.TYPE_SCREENSHOT: {
+                case WindowManager.LayoutParams.TYPE_SCREENSHOT:
+                case WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY: {
                     return AccessibilityWindowInfo.TYPE_SYSTEM;
                 }
 
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 42e859f..089861b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -192,7 +192,7 @@
     public AutofillManagerService(Context context) {
         super(context,
                 new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
-                UserManager.DISALLOW_AUTOFILL);
+                UserManager.DISALLOW_AUTOFILL, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
         mUi = new AutoFillUI(ActivityThread.currentActivityThread().getSystemUiContext());
         mAm = LocalServices.getService(ActivityManagerInternal.class);
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index efd3c3e..27d9ba0 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1908,8 +1908,11 @@
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
             String callingPackage, String caller) {
+        boolean hasModifyAudioSettings =
+                mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+                == PackageManager.PERMISSION_GRANTED;
         adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
-                caller, Binder.getCallingUid(), hasModifyAudioSettings(), VOL_ADJUST_NORMAL);
+                caller, Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL);
     }
 
     private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
@@ -2014,10 +2017,13 @@
                     + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
             return;
         }
+        final boolean hasModifyAudioSettings =
+                mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+                        == PackageManager.PERMISSION_GRANTED;
         sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
                 direction/*val1*/, flags/*val2*/, callingPackage));
         adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
-                Binder.getCallingUid(), hasModifyAudioSettings(), VOL_ADJUST_NORMAL);
+                Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL);
     }
 
     protected void adjustStreamVolume(int streamType, int direction, int flags,
@@ -2528,10 +2534,13 @@
                     + " MODIFY_AUDIO_ROUTING  callingPackage=" + callingPackage);
             return;
         }
+        final boolean hasModifyAudioSettings =
+                mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+                        == PackageManager.PERMISSION_GRANTED;
         sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
                 index/*val1*/, flags/*val2*/, callingPackage));
         setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
-                Binder.getCallingUid(), hasModifyAudioSettings());
+                Binder.getCallingUid(), hasModifyAudioSettings);
     }
 
     private boolean canChangeAccessibilityVolume() {
@@ -3197,7 +3206,8 @@
         ensureValidStreamType(streamType);
         final boolean isPrivileged =
                 Binder.getCallingUid() == Process.SYSTEM_UID
-                 || (hasModifyAudioSettings())
+                 || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+                        == PackageManager.PERMISSION_GRANTED)
                  || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
                         == PackageManager.PERMISSION_GRANTED);
         return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10;
@@ -4755,18 +4765,9 @@
         handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
     }
 
-    private boolean hasModifyAudioSettings() {
-        return mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private boolean hasModifyAudioSettings(int pid, int uid) {
-        return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
     boolean checkAudioSettingsPermission(String method) {
-        if (hasModifyAudioSettings()) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)
+                == PackageManager.PERMISSION_GRANTED) {
             return true;
         }
         String msg = "Audio Settings Permission Denial: " + method + " from pid="
@@ -7688,10 +7689,13 @@
         @Override
         public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags,
                 String callingPackage, int uid, int pid) {
+            final boolean hasModifyAudioSettings =
+                    mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+                    == PackageManager.PERMISSION_GRANTED;
             // direction and stream type swap here because the public
             // adjustSuggested has a different order than the other methods.
             adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage,
-                    callingPackage, uid, hasModifyAudioSettings(pid, uid), VOL_ADJUST_NORMAL);
+                    callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL);
         }
 
         @Override
@@ -7702,15 +7706,21 @@
                         direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
                         .append(" uid:").append(uid).toString()));
             }
+            final boolean hasModifyAudioSettings =
+                    mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+                            == PackageManager.PERMISSION_GRANTED;
             adjustStreamVolume(streamType, direction, flags, callingPackage,
-                    callingPackage, uid, hasModifyAudioSettings(pid, uid), VOL_ADJUST_NORMAL);
+                    callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL);
         }
 
         @Override
         public void setStreamVolumeForUid(int streamType, int direction, int flags,
                 String callingPackage, int uid, int pid) {
+            final boolean hasModifyAudioSettings =
+                    mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+                            == PackageManager.PERMISSION_GRANTED;
             setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid,
-                    hasModifyAudioSettings(pid, uid));
+                    hasModifyAudioSettings);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 9de95ab..b9669c74 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -40,6 +40,7 @@
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiHotplugEvent;
 import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.IHdmiControlService;
 import android.hardware.hdmi.IHdmiControlStatusChangeListener;
@@ -63,6 +64,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.PowerManager;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -268,6 +270,11 @@
     private final ArrayList<HdmiControlStatusChangeListenerRecord>
             mHdmiControlStatusChangeListenerRecords = new ArrayList<>();
 
+    // List of records for HDMI control volume control status change listener for death monitoring.
+    @GuardedBy("mLock")
+    private final RemoteCallbackList<IHdmiCecVolumeControlFeatureListener>
+            mHdmiCecVolumeControlFeatureListenerRecords = new RemoteCallbackList<>();
+
     // List of records for hotplug event listener to handle the the caller killed in action.
     @GuardedBy("mLock")
     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
@@ -1814,6 +1821,21 @@
         }
 
         @Override
+        public void addHdmiCecVolumeControlFeatureListener(
+                final IHdmiCecVolumeControlFeatureListener listener) {
+            enforceAccessPermission();
+            HdmiControlService.this.addHdmiCecVolumeControlFeatureListener(listener);
+        }
+
+        @Override
+        public void removeHdmiCecVolumeControlFeatureListener(
+                final IHdmiCecVolumeControlFeatureListener listener) {
+            enforceAccessPermission();
+            HdmiControlService.this.removeHdmiControlVolumeControlStatusChangeListener(listener);
+        }
+
+
+        @Override
         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
             enforceAccessPermission();
             HdmiControlService.this.addHotplugEventListener(listener);
@@ -2409,6 +2431,33 @@
         }
     }
 
+    @VisibleForTesting
+    void addHdmiCecVolumeControlFeatureListener(
+            final IHdmiCecVolumeControlFeatureListener listener) {
+        mHdmiCecVolumeControlFeatureListenerRecords.register(listener);
+
+        runOnServiceThread(new Runnable() {
+            @Override
+            public void run() {
+                // Return the current status of mHdmiCecVolumeControlEnabled;
+                synchronized (mLock) {
+                    try {
+                        listener.onHdmiCecVolumeControlFeature(mHdmiCecVolumeControlEnabled);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to report HdmiControlVolumeControlStatusChange: "
+                                + mHdmiCecVolumeControlEnabled, e);
+                    }
+                }
+            }
+        });
+    }
+
+    @VisibleForTesting
+    void removeHdmiControlVolumeControlStatusChangeListener(
+            final IHdmiCecVolumeControlFeatureListener listener) {
+        mHdmiCecVolumeControlFeatureListenerRecords.unregister(listener);
+    }
+
     private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
         final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
         try {
@@ -2682,6 +2731,19 @@
         }
     }
 
+    private void announceHdmiCecVolumeControlFeatureChange(boolean isEnabled) {
+        assertRunOnServiceThread();
+        mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
+            try {
+                listener.onHdmiCecVolumeControlFeature(isEnabled);
+            } catch (RemoteException e) {
+                Slog.e(TAG,
+                        "Failed to report HdmiControlVolumeControlStatusChange: "
+                                + isEnabled);
+            }
+        });
+    }
+
     public HdmiCecLocalDeviceTv tv() {
         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
     }
@@ -3026,6 +3088,7 @@
                         isHdmiCecVolumeControlEnabled);
             }
         }
+        announceHdmiCecVolumeControlFeatureChange(isHdmiCecVolumeControlEnabled);
     }
 
     boolean isHdmiCecVolumeControlEnabled() {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index ccbe96f..067bdcb 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -28,6 +28,9 @@
 
 import static com.android.server.location.CallerIdentity.PERMISSION_COARSE;
 import static com.android.server.location.CallerIdentity.PERMISSION_FINE;
+import static com.android.server.location.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
+import static com.android.server.location.UserInfoHelper.UserListener.USER_STARTED;
+import static com.android.server.location.UserInfoHelper.UserListener.USER_STOPPED;
 
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
@@ -64,6 +67,7 @@
 import android.location.LocationRequest;
 import android.location.LocationTime;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
@@ -101,7 +105,7 @@
 import com.android.server.location.CallerIdentity.PermissionLevel;
 import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
 import com.android.server.location.LocationRequestStatistics.PackageStatistics;
-import com.android.server.location.UserInfoHelper.UserListener;
+import com.android.server.location.UserInfoHelper.UserListener.UserChange;
 import com.android.server.location.gnss.GnssManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
@@ -132,11 +136,13 @@
      */
     public static class Lifecycle extends SystemService {
 
+        private final UserInfoHelper mUserInfoHelper;
         private final LocationManagerService mService;
 
         public Lifecycle(Context context) {
             super(context);
-            mService = new LocationManagerService(context);
+            mUserInfoHelper = new SystemUserInfoHelper(context);
+            mService = new LocationManagerService(context, mUserInfoHelper);
         }
 
         @Override
@@ -161,6 +167,29 @@
                 mService.onSystemThirdPartyAppsCanStart();
             }
         }
+
+        @Override
+        public void onUserStarting(TargetUser user) {
+            mUserInfoHelper.dispatchOnUserStarted(user.getUserIdentifier());
+        }
+
+        @Override
+        public void onUserSwitching(TargetUser from, TargetUser to) {
+            mUserInfoHelper.dispatchOnCurrentUserChanged(from.getUserIdentifier(),
+                    to.getUserIdentifier());
+        }
+
+        @Override
+        public void onUserStopped(TargetUser user) {
+            mUserInfoHelper.dispatchOnUserStopped(user.getUserIdentifier());
+        }
+
+        private static class SystemUserInfoHelper extends UserInfoHelper {
+
+            SystemUserInfoHelper(Context context) {
+                super(context);
+            }
+        }
     }
 
     public static final String TAG = "LocationManagerService";
@@ -232,7 +261,7 @@
     @PowerManager.LocationPowerSaveMode
     private int mBatterySaverMode;
 
-    private LocationManagerService(Context context) {
+    private LocationManagerService(Context context, UserInfoHelper userInfoHelper) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
         mHandler = FgThread.getHandler();
         mLocalService = new LocalService();
@@ -240,7 +269,7 @@
         LocalServices.addService(LocationManagerInternal.class, mLocalService);
 
         mAppOpsHelper = new AppOpsHelper(mContext);
-        mUserInfoHelper = new UserInfoHelper(mContext);
+        mUserInfoHelper = userInfoHelper;
         mSettingsHelper = new SettingsHelper(mContext, mHandler);
         mAppForegroundHelper = new AppForegroundHelper(mContext);
         mLocationUsageLogger = new LocationUsageLogger();
@@ -342,7 +371,7 @@
             // initialize the current users. we would get the user started notifications for these
             // users eventually anyways, but this takes care of it as early as possible.
             for (int userId: mUserInfoHelper.getCurrentUserIds()) {
-                onUserChanged(userId, UserListener.USER_STARTED);
+                onUserChanged(userId, USER_STARTED);
             }
         }
     }
@@ -596,32 +625,23 @@
         }
     }
 
-    private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
+    private void onUserChanged(@UserIdInt int userId, @UserChange int change) {
         switch (change) {
-            case UserListener.USER_SWITCHED:
-                if (D) {
-                    Log.d(TAG, "user " + userId + " current status changed");
-                }
+            case CURRENT_USER_CHANGED:
                 synchronized (mLock) {
                     for (LocationProviderManager manager : mProviderManagers) {
                         manager.onEnabledChangedLocked(userId);
                     }
                 }
                 break;
-            case UserListener.USER_STARTED:
-                if (D) {
-                    Log.d(TAG, "user " + userId + " started");
-                }
+            case USER_STARTED:
                 synchronized (mLock) {
                     for (LocationProviderManager manager : mProviderManagers) {
                         manager.onUserStarted(userId);
                     }
                 }
                 break;
-            case UserListener.USER_STOPPED:
-                if (D) {
-                    Log.d(TAG, "user " + userId + " stopped");
-                }
+            case USER_STOPPED:
                 synchronized (mLock) {
                     for (LocationProviderManager manager : mProviderManagers) {
                         manager.onUserStopped(userId);
@@ -957,10 +977,22 @@
                 pw.increaseIndent();
 
                 // for now we only dump for the parent user
-                int userId = mUserInfoHelper.getCurrentUserIds()[0];
-                pw.println("last location=" + mLastLocation.get(userId));
-                pw.println("last coarse location=" + mLastCoarseLocation.get(userId));
-                pw.println("enabled=" + isEnabled(userId));
+                int[] userIds = mUserInfoHelper.getCurrentUserIds();
+                if (userIds.length == 1) {
+                    int userId = userIds[0];
+                    pw.println("last location=" + mLastLocation.get(userId));
+                    pw.println("last coarse location=" + mLastCoarseLocation.get(userId));
+                    pw.println("enabled=" + isEnabled(userId));
+                } else {
+                    for (int userId : userIds) {
+                        pw.println("user " + userId + ":");
+                        pw.increaseIndent();
+                        pw.println("last location=" + mLastLocation.get(userId));
+                        pw.println("last coarse location=" + mLastCoarseLocation.get(userId));
+                        pw.println("enabled=" + isEnabled(userId));
+                        pw.decreaseIndent();
+                    }
+                }
             }
 
             mProvider.dump(fd, pw, args);
@@ -1666,6 +1698,9 @@
          * Note: must be constructed with lock held.
          */
         private UpdateRecord(String provider, LocationRequest request, Receiver receiver) {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
             mExpirationRealtimeMs = request.getExpirationRealtimeMs(SystemClock.elapsedRealtime());
             mProvider = provider;
             mRealRequest = request;
@@ -1703,6 +1738,10 @@
          * Method to be called when a record will no longer be used.
          */
         private void disposeLocked(boolean removeReceiver) {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
             CallerIdentity identity = mReceiver.mCallerIdentity;
             mRequestStatistics.stopRequesting(identity.packageName, identity.featureId, mProvider);
 
diff --git a/services/core/java/com/android/server/location/UserInfoHelper.java b/services/core/java/com/android/server/location/UserInfoHelper.java
index a3dcc40..53bff8e 100644
--- a/services/core/java/com/android/server/location/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/UserInfoHelper.java
@@ -20,48 +20,48 @@
 
 import static com.android.server.location.LocationManagerService.D;
 import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
+import static com.android.server.location.UserInfoHelper.UserListener.USER_STARTED;
+import static com.android.server.location.UserInfoHelper.UserListener.USER_STOPPED;
 
+import android.annotation.CallSuper;
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
+import android.app.ActivityManagerInternal;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.Binder;
-import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
-import com.android.server.FgThread;
+import com.android.server.LocalServices;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Provides accessors and listeners for all user info.
  */
-public class UserInfoHelper {
+public abstract class UserInfoHelper {
 
     /**
      * Listener for current user changes.
      */
     public interface UserListener {
 
-        int USER_SWITCHED = 1;
+        int CURRENT_USER_CHANGED = 1;
         int USER_STARTED = 2;
         int USER_STOPPED = 3;
 
-        @IntDef({USER_SWITCHED, USER_STARTED, USER_STOPPED})
+        @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED})
         @Retention(RetentionPolicy.SOURCE)
         @interface UserChange {}
 
@@ -75,143 +75,101 @@
     private final CopyOnWriteArrayList<UserListener> mListeners;
 
     @GuardedBy("this")
+    @Nullable private ActivityManagerInternal mActivityManagerInternal;
+    @GuardedBy("this")
     @Nullable private UserManager mUserManager;
 
-    @UserIdInt private volatile int mCurrentUserId;
-
-    @GuardedBy("this")
-    @UserIdInt private int mCachedParentUserId;
-    @GuardedBy("this")
-    private int[] mCachedProfileUserIds;
-
     public UserInfoHelper(Context context) {
         mContext = context;
         mListeners = new CopyOnWriteArrayList<>();
-
-        mCurrentUserId = UserHandle.USER_NULL;
-        mCachedParentUserId = UserHandle.USER_NULL;
-        mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
     }
 
     /** Called when system is ready. */
+    @CallSuper
     public synchronized void onSystemReady() {
-        if (mUserManager != null) {
+        if (mActivityManagerInternal != null) {
             return;
         }
 
+        mActivityManagerInternal = Objects.requireNonNull(
+                LocalServices.getService(ActivityManagerInternal.class));
         mUserManager = mContext.getSystemService(UserManager.class);
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
-        intentFilter.addAction(Intent.ACTION_USER_STARTED);
-        intentFilter.addAction(Intent.ACTION_USER_STOPPED);
-        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-
-        mContext.registerReceiverAsUser(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                if (action == null) {
-                    return;
-                }
-                int userId;
-                switch (action) {
-                    case Intent.ACTION_USER_SWITCHED:
-                        userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                        if (userId != UserHandle.USER_NULL) {
-                            onCurrentUserChanged(userId);
-                        }
-                        break;
-                    case Intent.ACTION_USER_STARTED:
-                        userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                        if (userId != UserHandle.USER_NULL) {
-                            onUserStarted(userId);
-                        }
-                        break;
-                    case Intent.ACTION_USER_STOPPED:
-                        userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                        if (userId != UserHandle.USER_NULL) {
-                            onUserStopped(userId);
-                        }
-                        break;
-                    case Intent.ACTION_MANAGED_PROFILE_ADDED:
-                    case Intent.ACTION_MANAGED_PROFILE_REMOVED:
-                        onUserProfilesChanged();
-                        break;
-                }
-            }
-        }, UserHandle.ALL, intentFilter, null, FgThread.getHandler());
-
-        mCurrentUserId = ActivityManager.getCurrentUser();
     }
 
     /**
      * Adds a listener for user changed events. Callbacks occur on an unspecified thread.
      */
-    public void addListener(UserListener listener) {
+    public final void addListener(UserListener listener) {
         mListeners.add(listener);
     }
 
     /**
      * Removes a listener for user changed events.
      */
-    public void removeListener(UserListener listener) {
+    public final void removeListener(UserListener listener) {
         mListeners.remove(listener);
     }
 
-    private void onCurrentUserChanged(@UserIdInt int newUserId) {
-        if (newUserId == mCurrentUserId) {
-            return;
-        }
-
-        if (D) {
-            Log.d(TAG, "current user switched from u" + mCurrentUserId + " to u" + newUserId);
-        }
-
-        int oldUserId = mCurrentUserId;
-        mCurrentUserId = newUserId;
-
-        onUserChanged(oldUserId, UserListener.USER_SWITCHED);
-        onUserChanged(newUserId, UserListener.USER_SWITCHED);
-    }
-
-    private void onUserStarted(@UserIdInt int userId) {
+    protected void dispatchOnUserStarted(@UserIdInt int userId) {
         if (D) {
             Log.d(TAG, "u" + userId + " started");
         }
 
-        onUserChanged(userId, UserListener.USER_STARTED);
+        for (UserListener listener : mListeners) {
+            listener.onUserChanged(userId, USER_STARTED);
+        }
     }
 
-    private void onUserStopped(@UserIdInt int userId) {
+    protected void dispatchOnUserStopped(@UserIdInt int userId) {
         if (D) {
             Log.d(TAG, "u" + userId + " stopped");
         }
 
-        onUserChanged(userId, UserListener.USER_STOPPED);
-    }
-
-    private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
         for (UserListener listener : mListeners) {
-            listener.onUserChanged(userId, change);
+            listener.onUserChanged(userId, USER_STOPPED);
         }
     }
 
-    private synchronized void onUserProfilesChanged() {
-        // this intent is only sent to the current user
-        if (mCachedParentUserId == mCurrentUserId) {
-            mCachedParentUserId = UserHandle.USER_NULL;
-            mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+    protected void dispatchOnCurrentUserChanged(@UserIdInt int fromUserId,
+            @UserIdInt int toUserId) {
+        int[] fromUserIds = getProfileIds(fromUserId);
+        int[] toUserIds = getProfileIds(toUserId);
+        if (D) {
+            Log.d(TAG, "current user changed from u" + Arrays.toString(fromUserIds) + " to u"
+                    + Arrays.toString(toUserIds));
+        }
+
+        for (UserListener listener : mListeners) {
+            for (int userId : fromUserIds) {
+                listener.onUserChanged(userId, CURRENT_USER_CHANGED);
+            }
+        }
+
+        for (UserListener listener : mListeners) {
+            for (int userId : toUserIds) {
+                listener.onUserChanged(userId, CURRENT_USER_CHANGED);
+            }
         }
     }
 
     /**
      * Returns an array of current user ids. This will always include the current user, and will
-     * also include any profiles of the current user.
+     * also include any profiles of the current user. The caller must never mutate the returned
+     * array.
      */
     public int[] getCurrentUserIds() {
-        return getProfileUserIdsForParentUser(mCurrentUserId);
+        synchronized (this) {
+            if (mActivityManagerInternal == null) {
+                return new int[] {};
+            }
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            return mActivityManagerInternal.getCurrentProfileIds();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
@@ -219,54 +177,47 @@
      * user.
      */
     public boolean isCurrentUserId(@UserIdInt int userId) {
-        int currentUserId = mCurrentUserId;
-        return userId == currentUserId || ArrayUtils.contains(
-                getProfileUserIdsForParentUser(currentUserId), userId);
-    }
-
-    @GuardedBy("this")
-    private synchronized int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) {
-        if (parentUserId != mCachedParentUserId) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                Preconditions.checkState(mUserManager != null);
-
-                // more expensive check - check that argument really is a parent user id
-                if (Build.IS_DEBUGGABLE) {
-                    Preconditions.checkArgument(
-                            mUserManager.getProfileParent(parentUserId) == null);
-                }
-
-                mCachedParentUserId = parentUserId;
-                mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+        synchronized (this) {
+            if (mActivityManagerInternal == null) {
+                return false;
             }
         }
 
-        return mCachedProfileUserIds;
+        long identity = Binder.clearCallingIdentity();
+        try {
+            return mActivityManagerInternal.isCurrentProfile(userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private int[] getProfileIds(@UserIdInt int userId) {
+        synchronized (this) {
+            Preconditions.checkState(mUserManager != null);
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            return mUserManager.getEnabledProfileIds(userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
      * Dump info for debugging.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        boolean systemRunning;
-        synchronized (this) {
-            systemRunning = mUserManager != null;
-        }
-
-        if (systemRunning) {
-            int[] currentUserIds = getProfileUserIdsForParentUser(mCurrentUserId);
-            pw.println("current users: " + Arrays.toString(currentUserIds));
-            for (int userId : currentUserIds) {
-                if (mUserManager.hasUserRestrictionForUser(DISALLOW_SHARE_LOCATION,
+        int[] currentUserProfiles = getCurrentUserIds();
+        pw.println("current users: " + Arrays.toString(currentUserProfiles));
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userManager != null) {
+            for (int userId : currentUserProfiles) {
+                if (userManager.hasUserRestrictionForUser(DISALLOW_SHARE_LOCATION,
                         UserHandle.of(userId))) {
                     pw.println("  u" + userId + " restricted");
                 }
             }
-        } else {
-            pw.println("current user: " + mCurrentUserId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
index af8baa5..d323d80 100644
--- a/services/core/java/com/android/server/notification/BadgeExtractor.java
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.util.Slog;
+import android.app.Notification;
 
 /**
  * Determines whether a badge should be shown for this notification
@@ -61,6 +62,10 @@
             record.setShowBadge(false);
         }
 
+        Notification.BubbleMetadata metadata = record.getNotification().getBubbleMetadata();
+        if (metadata != null && metadata.isNotificationSuppressed()) {
+            record.setShowBadge(false);
+        }
         return null;
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9d3385f..a95dc30 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -246,6 +246,7 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
@@ -2173,19 +2174,19 @@
         mStatsManager.setPullAtomCallback(
                 PACKAGE_NOTIFICATION_PREFERENCES,
                 null, // use default PullAtomMetadata values
-                BackgroundThread.getExecutor(),
+                ConcurrentUtils.DIRECT_EXECUTOR,
                 mPullAtomCallback
         );
         mStatsManager.setPullAtomCallback(
                 PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES,
                 null, // use default PullAtomMetadata values
-                BackgroundThread.getExecutor(),
+                ConcurrentUtils.DIRECT_EXECUTOR,
                 mPullAtomCallback
         );
         mStatsManager.setPullAtomCallback(
                 PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES,
                 null, // use default PullAtomMetadata values
-                BackgroundThread.getExecutor(),
+                ConcurrentUtils.DIRECT_EXECUTOR,
                 mPullAtomCallback
         );
     }
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 59735eb..d6b1b27 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -65,7 +65,7 @@
      * modified.
      */
     boolean createIdmap(@NonNull final PackageInfo targetPackage,
-            @NonNull final PackageInfo overlayPackage, int additionalPolicies, int userId) {
+            @NonNull final PackageInfo overlayPackage, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
                     + overlayPackage.packageName);
@@ -73,14 +73,13 @@
         final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
         final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
         try {
+            int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
             boolean enforce = enforceOverlayable(overlayPackage);
-            int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId)
-                    | additionalPolicies;
             if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
                 return false;
             }
-            return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies, enforce, userId)
-                    != null;
+            return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
+                    enforce, userId) != null;
         } catch (Exception e) {
             Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
                     + overlayPath + ": " + e.getMessage());
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 3c5e476..3968153 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -45,7 +45,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.content.res.ApkAssets;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
@@ -63,7 +62,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.R;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.server.FgThread;
 import com.android.server.IoThread;
@@ -252,8 +250,7 @@
             mSettings = new OverlayManagerSettings();
             mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
                     OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(),
-                    new OverlayChangeListener(), getOverlayableConfigurator(),
-                    getOverlayableConfiguratorTargets());
+                    new OverlayChangeListener());
             mActorEnforcer = new OverlayActorEnforcer(mPackageManager);
 
             final IntentFilter packageFilter = new IntentFilter();
@@ -336,28 +333,6 @@
         return defaultPackages.toArray(new String[defaultPackages.size()]);
     }
 
-
-    /**
-     * Retrieves the package name that is recognized as an actor for the packages specified by
-     * {@link #getOverlayableConfiguratorTargets()}.
-     */
-    @Nullable
-    private String getOverlayableConfigurator() {
-        return TextUtils.nullIfEmpty(Resources.getSystem()
-                .getString(R.string.config_overlayableConfigurator));
-    }
-
-    /**
-     * Retrieves the target packages that recognize the {@link #getOverlayableConfigurator} as an
-     * actor for itself. Overlays targeting one of the specified targets that are signed with the
-     * same signature as the overlayable configurator will be granted the "actor" policy.
-     */
-    @Nullable
-    private String[] getOverlayableConfiguratorTargets() {
-        return Resources.getSystem().getStringArray(
-                R.array.config_overlayableConfiguratorTargets);
-    }
-
     private final class PackageReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 879ad4f..05a4a38 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -31,7 +31,6 @@
 import android.content.om.OverlayInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.os.OverlayablePolicy;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -74,9 +73,6 @@
     private final String[] mDefaultOverlays;
     private final OverlayChangeListener mListener;
 
-    private final String mOverlayableConfigurator;
-    private final String[] mOverlayableConfiguratorTargets;
-
     /**
      * Helper method to merge the overlay manager's (as read from overlays.xml)
      * and package manager's (as parsed from AndroidManifest.xml files) views
@@ -119,17 +115,13 @@
             @NonNull final OverlayManagerSettings settings,
             @NonNull final OverlayConfig overlayConfig,
             @NonNull final String[] defaultOverlays,
-            @NonNull final OverlayChangeListener listener,
-            @Nullable final String overlayableConfigurator,
-            @Nullable final String[] overlayableConfiguratorTargets) {
+            @NonNull final OverlayChangeListener listener) {
         mPackageManager = packageManager;
         mIdmapManager = idmapManager;
         mSettings = settings;
         mOverlayConfig = overlayConfig;
         mDefaultOverlays = defaultOverlays;
         mListener = listener;
-        mOverlayableConfigurator = overlayableConfigurator;
-        mOverlayableConfiguratorTargets = overlayableConfiguratorTargets;
     }
 
     /**
@@ -714,25 +706,7 @@
         if (targetPackage != null && overlayPackage != null
                 && !("android".equals(targetPackageName)
                     && !isPackageConfiguredMutable(overlayPackageName))) {
-
-            int additionalPolicies = 0;
-            if (TextUtils.nullIfEmpty(mOverlayableConfigurator) != null
-                    && ArrayUtils.contains(mOverlayableConfiguratorTargets, targetPackageName)
-                    && isPackageConfiguredMutable(overlayPackageName)
-                    && mPackageManager.signaturesMatching(mOverlayableConfigurator,
-                            overlayPackageName, userId)) {
-                // The overlay targets a package that has the overlayable configurator configured as
-                // its actor. The overlay and this actor are signed with the same signature, so
-                // the overlay fulfills the actor policy.
-                modified |= mSettings.setHasConfiguratorActorPolicy(overlayPackageName, userId,
-                        true);
-                additionalPolicies |= OverlayablePolicy.ACTOR_SIGNATURE;
-            } else if (mSettings.hasConfiguratorActorPolicy(overlayPackageName, userId)) {
-                additionalPolicies |= OverlayablePolicy.ACTOR_SIGNATURE;
-            }
-
-            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, additionalPolicies,
-                    userId);
+            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
         }
 
         if (overlayPackage != null) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index f8226fa..3d520bf 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -73,7 +73,7 @@
         remove(packageName, userId);
         insert(new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
                 baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, isMutable, priority,
-                overlayCategory, false /* hasConfiguratorActorPolicy */));
+                overlayCategory));
     }
 
     /**
@@ -160,26 +160,6 @@
         return mItems.get(idx).setState(state);
     }
 
-    boolean hasConfiguratorActorPolicy(@NonNull final String packageName, final int userId) {
-        final int idx = select(packageName, userId);
-        if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
-        }
-        return mItems.get(idx).hasConfiguratorActorPolicy();
-    }
-
-    /**
-     * Returns true if the settings were modified, false if they remain the same.
-     */
-    boolean setHasConfiguratorActorPolicy(@NonNull final String packageName, final int userId,
-            boolean hasPolicy) {
-        final int idx = select(packageName, userId);
-        if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
-        }
-        return mItems.get(idx).setHasConfiguratorActorPolicy(hasPolicy);
-    }
-
     List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
             final int userId) {
         // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
@@ -343,17 +323,16 @@
         pw.println(item.mPackageName + ":" + item.getUserId() + " {");
         pw.increaseIndent();
 
-        pw.println("mPackageName................: " + item.mPackageName);
-        pw.println("mUserId.....................: " + item.getUserId());
-        pw.println("mTargetPackageName..........: " + item.getTargetPackageName());
-        pw.println("mTargetOverlayableName......: " + item.getTargetOverlayableName());
-        pw.println("mBaseCodePath...............: " + item.getBaseCodePath());
-        pw.println("mState......................: " + OverlayInfo.stateToString(item.getState()));
-        pw.println("mIsEnabled..................: " + item.isEnabled());
-        pw.println("mIsMutable..................: " + item.isMutable());
-        pw.println("mPriority...................: " + item.mPriority);
-        pw.println("mCategory...................: " + item.mCategory);
-        pw.println("mHasConfiguratorActorPolicy.: " + item.hasConfiguratorActorPolicy());
+        pw.println("mPackageName...........: " + item.mPackageName);
+        pw.println("mUserId................: " + item.getUserId());
+        pw.println("mTargetPackageName.....: " + item.getTargetPackageName());
+        pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName());
+        pw.println("mBaseCodePath..........: " + item.getBaseCodePath());
+        pw.println("mState.................: " + OverlayInfo.stateToString(item.getState()));
+        pw.println("mIsEnabled.............: " + item.isEnabled());
+        pw.println("mIsMutable.............: " + item.isMutable());
+        pw.println("mPriority..............: " + item.mPriority);
+        pw.println("mCategory..............: " + item.mCategory);
 
         pw.decreaseIndent();
         pw.println("}");
@@ -392,9 +371,6 @@
             case "category":
                 pw.println(item.mCategory);
                 break;
-            case "hasconfiguratoractorpolicy":
-                pw.println(item.mHasConfiguratorActorPolicy);
-                break;
         }
     }
 
@@ -422,8 +398,6 @@
         private static final String ATTR_CATEGORY = "category";
         private static final String ATTR_USER_ID = "userId";
         private static final String ATTR_VERSION = "version";
-        private static final String ATTR_HAS_CONFIGURATOR_ACTOR_POLICY =
-                "hasConfiguratorActorPolicy";
 
         @VisibleForTesting
         static final int CURRENT_VERSION = 4;
@@ -461,6 +435,10 @@
                     // Throw an exception which will cause the overlay file to be ignored
                     // and overwritten.
                     throw new XmlPullParserException("old version " + oldVersion + "; ignoring");
+                case 3:
+                    // Upgrading from version 3 to 4 is not a breaking change so do not ignore the
+                    // overlay file.
+                    return;
                 default:
                     throw new XmlPullParserException("unrecognized version " + oldVersion);
             }
@@ -480,12 +458,9 @@
             final boolean isStatic = XmlUtils.readBooleanAttribute(parser, ATTR_IS_STATIC);
             final int priority = XmlUtils.readIntAttribute(parser, ATTR_PRIORITY);
             final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
-            final boolean hasConfiguratorActorPolicy = XmlUtils.readBooleanAttribute(parser,
-                    ATTR_HAS_CONFIGURATOR_ACTOR_POLICY);
 
             return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
-                    baseCodePath, state, isEnabled, !isStatic, priority, category,
-                    hasConfiguratorActorPolicy);
+                    baseCodePath, state, isEnabled, !isStatic, priority, category);
         }
 
         public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -520,8 +495,6 @@
             XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable);
             XmlUtils.writeIntAttribute(xml, ATTR_PRIORITY, item.mPriority);
             XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
-            XmlUtils.writeBooleanAttribute(xml, ATTR_HAS_CONFIGURATOR_ACTOR_POLICY,
-                    item.mHasConfiguratorActorPolicy);
             xml.endTag(null, TAG_ITEM);
         }
     }
@@ -538,14 +511,12 @@
         private boolean mIsMutable;
         private int mPriority;
         private String mCategory;
-        private boolean mHasConfiguratorActorPolicy;
 
         SettingsItem(@NonNull final String packageName, final int userId,
                 @NonNull final String targetPackageName,
                 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
                 final @OverlayInfo.State int state, final boolean isEnabled,
-                final boolean isMutable, final int priority,  @Nullable String category,
-                final boolean hasConfiguratorActorPolicy) {
+                final boolean isMutable, final int priority,  @Nullable String category) {
             mPackageName = packageName;
             mUserId = userId;
             mTargetPackageName = targetPackageName;
@@ -557,7 +528,6 @@
             mCache = null;
             mIsMutable = isMutable;
             mPriority = priority;
-            mHasConfiguratorActorPolicy = hasConfiguratorActorPolicy;
         }
 
         private String getTargetPackageName() {
@@ -648,18 +618,6 @@
         private int getPriority() {
             return mPriority;
         }
-
-        private boolean hasConfiguratorActorPolicy() {
-            return mHasConfiguratorActorPolicy;
-        }
-
-        private boolean setHasConfiguratorActorPolicy(boolean hasPolicy) {
-            if (mHasConfiguratorActorPolicy != hasPolicy) {
-                mHasConfiguratorActorPolicy = hasPolicy;
-                return true;
-            }
-            return false;
-        }
     }
 
     private int select(@NonNull final String packageName, final int userId) {
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 81ee7d9..52fdc79 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderParamsParcel;
 import android.content.pm.IDataLoader;
 import android.content.pm.IDataLoaderManager;
@@ -122,19 +121,7 @@
                         ri.serviceInfo.packageName, ri.serviceInfo.name);
                 // There should only be one matching provider inside the given package.
                 // If there's more than one, return the first one found.
-                try {
-                    ApplicationInfo ai = pm.getApplicationInfo(resolved.getPackageName(), 0);
-                    if (!ai.isPrivilegedApp()) {
-                        Slog.w(TAG,
-                                "Data loader: " + resolved + " is not a privileged app, skipping.");
-                        continue;
-                    }
-                    return resolved;
-                } catch (PackageManager.NameNotFoundException ex) {
-                    Slog.w(TAG,
-                            "Privileged data loader: " + resolved + " not found, skipping.");
-                }
-
+                return resolved;
             }
             Slog.e(TAG, "Didn't find any matching data loader service provider.");
             return null;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 236a681..f827721 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -40,6 +40,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.VersionedPackage;
@@ -126,8 +127,10 @@
     private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
     /** Automatically destroy staged sessions that have not changed state in this time */
     private static final long MAX_TIME_SINCE_UPDATE_MILLIS = 7 * DateUtils.DAY_IN_MILLIS;
-    /** Upper bound on number of active sessions for a UID */
-    private static final long MAX_ACTIVE_SESSIONS = 1024;
+    /** Upper bound on number of active sessions for a UID that has INSTALL_PACKAGES */
+    private static final long MAX_ACTIVE_SESSIONS_WITH_PERMISSION = 1024;
+    /** Upper bound on number of active sessions for a UID without INSTALL_PACKAGES */
+    private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50;
     /** Upper bound on number of historical sessions for a UID */
     private static final long MAX_HISTORICAL_SESSIONS = 1048576;
 
@@ -503,7 +506,18 @@
                     + "to use a data loader");
         }
 
-        String requestedInstallerPackageName = params.installerPackageName != null
+        // App package name and label length is restricted so that really long strings aren't
+        // written to disk.
+        if (params.appPackageName != null
+                && params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
+            params.appPackageName = null;
+        }
+
+        params.appLabel = TextUtils.trimToSize(params.appLabel,
+                PackageItemInfo.MAX_SAFE_LABEL_LENGTH);
+
+        String requestedInstallerPackageName = (params.installerPackageName != null
+                && params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
                 ? params.installerPackageName : installerPackageName;
 
         if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
@@ -635,12 +649,23 @@
             }
         }
 
+        if (params.whitelistedRestrictedPermissions != null) {
+            mPermissionManager.retainHardAndSoftRestrictedPermissions(
+                    params.whitelistedRestrictedPermissions);
+        }
+
         final int sessionId;
         final PackageInstallerSession session;
         synchronized (mSessions) {
             // Sanity check that installer isn't going crazy
             final int activeCount = getSessionCount(mSessions, callingUid);
-            if (activeCount >= MAX_ACTIVE_SESSIONS) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+                    == PackageManager.PERMISSION_GRANTED) {
+                if (activeCount >= MAX_ACTIVE_SESSIONS_WITH_PERMISSION) {
+                    throw new IllegalStateException(
+                            "Too many active sessions for UID " + callingUid);
+                }
+            } else if (activeCount >= MAX_ACTIVE_SESSIONS_NO_PERMISSION) {
                 throw new IllegalStateException(
                         "Too many active sessions for UID " + callingUid);
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 64d3015..766fae6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -148,6 +148,8 @@
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
 import android.app.backup.IBackupManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -639,6 +641,19 @@
      */
     private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW;
 
+    /**
+     * Adding an installer package name to a package that does not have one set requires the
+     * INSTALL_PACKAGES permission.
+     *
+     * If the caller targets R, this will throw a SecurityException. Otherwise the request will
+     * fail silently. In both cases, and regardless of whether this change is enabled, the
+     * installer package will remain unchanged.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE =
+            150857253;
+
     public static final String PLATFORM_PACKAGE_NAME = "android";
 
     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
@@ -14172,19 +14187,38 @@
             // be signed with the same cert as the caller.
             String targetInstallerPackageName =
                     targetPackageSetting.installSource.installerPackageName;
-            if (targetInstallerPackageName != null) {
-                PackageSetting setting = mSettings.mPackages.get(
-                        targetInstallerPackageName);
-                // If the currently set package isn't valid, then it's always
-                // okay to change it.
-                if (setting != null) {
-                    if (compareSignatures(callerSignature,
-                            setting.signatures.mSigningDetails.signatures)
-                            != PackageManager.SIGNATURE_MATCH) {
-                        throw new SecurityException(
-                                "Caller does not have same cert as old installer package "
-                                + targetInstallerPackageName);
+            PackageSetting targetInstallerPkgSetting = targetInstallerPackageName == null ? null :
+                    mSettings.mPackages.get(targetInstallerPackageName);
+
+            if (targetInstallerPkgSetting != null) {
+                if (compareSignatures(callerSignature,
+                        targetInstallerPkgSetting.signatures.mSigningDetails.signatures)
+                        != PackageManager.SIGNATURE_MATCH) {
+                    throw new SecurityException(
+                            "Caller does not have same cert as old installer package "
+                            + targetInstallerPackageName);
+                }
+            } else if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+                    != PackageManager.PERMISSION_GRANTED) {
+                // This is probably an attempt to exploit vulnerability b/150857253 of taking
+                // privileged installer permissions when the installer has been uninstalled or
+                // was never set.
+                EventLog.writeEvent(0x534e4554, "150857253", callingUid, "");
+
+                long binderToken = Binder.clearCallingIdentity();
+                try {
+                    if (mInjector.getCompatibility().isChangeEnabledByUid(
+                            THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE,
+                            callingUid)) {
+                        throw new SecurityException("Neither user " + callingUid
+                                + " nor current process has "
+                                + Manifest.permission.INSTALL_PACKAGES);
+                    } else {
+                        // If change disabled, fail silently for backwards compatibility
+                        return;
                     }
+                } finally {
+                    Binder.restoreCallingIdentity(binderToken);
                 }
             }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d5c9424..40fa798 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2250,9 +2250,6 @@
         // Managed profiles have their own specific rules.
         final boolean isManagedProfile = type.isManagedProfile();
         if (isManagedProfile) {
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                return false;
-            }
             if (!mContext.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_MANAGED_USERS)) {
                 return false;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 0a1d236..d3f3ba1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4950,6 +4950,20 @@
                         StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
             }
         }
+
+        @Override
+        public void retainHardAndSoftRestrictedPermissions(@NonNull List<String> permissions) {
+            synchronized (mLock) {
+                Iterator<String> iterator = permissions.iterator();
+                while (iterator.hasNext()) {
+                    String permission = iterator.next();
+                    BasePermission basePermission = mSettings.mPermissions.get(permission);
+                    if (basePermission == null || !basePermission.isHardOrSoftRestricted()) {
+                        iterator.remove();
+                    }
+                }
+            }
+        }
     }
 
     private static final class OnPermissionChangeListeners extends Handler {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 57a25ed..4412162 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -36,6 +36,7 @@
  * TODO: Should be merged into PermissionManagerInternal, but currently uses internal classes.
  */
 public abstract class PermissionManagerServiceInternal extends PermissionManagerInternal {
+
     /**
      * Provider for package names.
      */
@@ -455,4 +456,10 @@
 
     /** Called when a new user has been created. */
     public abstract void onNewUserCreated(@UserIdInt int userId);
+
+    /**
+     * Removes invalid permissions which are not {@link PermissionInfo#FLAG_HARD_RESTRICTED} or
+     * {@link PermissionInfo#FLAG_SOFT_RESTRICTED} from the input.
+     */
+    public abstract void retainHardAndSoftRestrictedPermissions(@NonNull List<String> permissions);
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 49c7819..2f963b7 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -20,11 +20,16 @@
 import android.annotation.Nullable;
 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
+import android.media.audio.common.AudioConfig;
+import android.media.audio.common.AudioOffloadInfo;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
 import android.media.soundtrigger_middleware.PhraseSoundModel;
 import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundModelType;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
@@ -540,20 +545,20 @@
                     switch (mModelType) {
                         case SoundModelType.GENERIC: {
                             android.media.soundtrigger_middleware.RecognitionEvent event =
-                                    new android.media.soundtrigger_middleware.RecognitionEvent();
+                                    newEmptyRecognitionEvent();
                             event.status =
                                     android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+                            event.type = SoundModelType.GENERIC;
                             mCallback.onRecognition(mHandle, event);
                         }
                         break;
 
                         case SoundModelType.KEYPHRASE: {
                             android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
-                                    new android.media.soundtrigger_middleware.PhraseRecognitionEvent();
-                            event.common =
-                                    new android.media.soundtrigger_middleware.RecognitionEvent();
+                                    newEmptyPhraseRecognitionEvent();
                             event.common.status =
                                     android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+                            event.common.type = SoundModelType.KEYPHRASE;
                             mCallback.onPhraseRecognition(mHandle, event);
                         }
                         break;
@@ -614,4 +619,35 @@
             }
         }
     }
+
+    /**
+     * Creates a default-initialized recognition event.
+     *
+     * Object fields are default constructed.
+     * Array fields are initialized to 0 length.
+     *
+     * @return The event.
+     */
+    private static RecognitionEvent newEmptyRecognitionEvent() {
+        RecognitionEvent result = new RecognitionEvent();
+        result.audioConfig = new AudioConfig();
+        result.audioConfig.offloadInfo = new AudioOffloadInfo();
+        result.data = new byte[0];
+        return result;
+    }
+
+    /**
+     * Creates a default-initialized phrase recognition event.
+     *
+     * Object fields are default constructed.
+     * Array fields are initialized to 0 length.
+     *
+     * @return The event.
+     */
+    private static PhraseRecognitionEvent newEmptyPhraseRecognitionEvent() {
+        PhraseRecognitionEvent result = new PhraseRecognitionEvent();
+        result.common = newEmptyRecognitionEvent();
+        result.phraseExtras = new PhraseRecognitionExtra[0];
+        return result;
+    }
 }
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 5f63233..c38d649 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -115,7 +115,7 @@
     private static final String TAG = "UriGrantsManagerService";
     // Maximum number of persisted Uri grants a package is allowed
     private static final int MAX_PERSISTED_URI_GRANTS = 128;
-    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
+    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
 
     private final Object mLock = new Object();
     private final H mH;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3189705..48609e1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3164,6 +3164,11 @@
     }
 
     @Override
+    public SurfaceControl.Builder makeAnimationLeash() {
+        return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId);
+    }
+
+    @Override
     public SurfaceControl getAnimationLeashParent() {
         if (WindowManagerService.sHierarchicalAnimations) {
             return super.getAnimationLeashParent();
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index df0fa9c..6e9428e 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -61,6 +61,7 @@
     private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 26;
     private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 27;
     private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 28;
+    private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 29;
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -183,6 +184,10 @@
         l.onTaskRequestedOrientationChanged(m.arg1, m.arg2);
     };
 
+    private final TaskStackConsumer mNotifyOnActivityRotation = (l, m) -> {
+        l.onActivityRotation();
+    };
+
     @FunctionalInterface
     public interface TaskStackConsumer {
         void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -277,6 +282,9 @@
                 case NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG:
                     forAllRemoteListeners(mNotifyTaskRequestedOrientationChanged, msg);
                     break;
+                case NOTIFY_ACTIVITY_ROTATED_MSG:
+                    forAllRemoteListeners(mNotifyOnActivityRotation, msg);
+                    break;
             }
             if (msg.obj instanceof SomeArgs) {
                 ((SomeArgs) msg.obj).recycle();
@@ -574,4 +582,11 @@
         forAllLocalListeners(mNotifyTaskRequestedOrientationChanged, msg);
         msg.sendToTarget();
     }
+
+    /** @see android.app.ITaskStackListener#onActivityRotation() */
+    void notifyOnActivityRotation() {
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_ROTATED_MSG);
+        forAllLocalListeners(mNotifyOnActivityRotation, msg);
+        msg.sendToTarget();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 3925570..e26f1e1 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -391,6 +391,7 @@
             frame = null;
             mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
             mTmpDstFrame.set(mFrame);
+            mTmpDstFrame.offsetTo(0, 0);
         }
 
         // Scale the mismatch dimensions to fill the task bounds
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f34510e..8934e8f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3848,6 +3848,11 @@
                     final boolean rotationChanged = displayContent.updateRotationUnchecked();
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
+                    if (rotationChanged) {
+                        mAtmService.getTaskChangeNotificationController()
+                                .notifyOnActivityRotation();
+                    }
+
                     if (!rotationChanged || forceRelayout) {
                         displayContent.setLayoutNeeded();
                         layoutNeeded = true;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0450890..36232e1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5790,10 +5790,10 @@
         // be invoked and we need to invoke it ourself.
         if (mLocalSyncId >= 0) {
             mBLASTSyncEngine.setReady(mLocalSyncId);
-        } else {
-            mWaitingListener.onTransactionReady(mWaitingSyncId, mBLASTSyncTransaction);
+            return mWinAnimator.finishDrawingLocked(null);
         }
 
+        mWaitingListener.onTransactionReady(mWaitingSyncId, mBLASTSyncTransaction);
         mUsingBLASTSyncTransaction = false;
 
         mWaitingSyncId = 0;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 42c2193..c025236 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -418,25 +418,25 @@
         if (!mDestroyPreservedSurfaceUponRedraw) {
             return;
         }
-        if (mSurfaceController != null) {
-            if (mPendingDestroySurface != null) {
-                // If we are preserving a surface but we aren't relaunching that means
-                // we are just doing an in-place switch. In that case any SurfaceFlinger side
-                // child layers need to be reparented to the new surface to make this
-                // transparent to the app.
-                if (mWin.mActivityRecord == null || mWin.mActivityRecord.isRelaunching() == false) {
-                    mPostDrawTransaction.reparentChildren(
-                        mPendingDestroySurface.getClientViewRootSurface(),
-                        mSurfaceController.mSurfaceControl).apply();
-                }
-            }
+
+        // If we are preserving a surface but we aren't relaunching that means
+        // we are just doing an in-place switch. In that case any SurfaceFlinger side
+        // child layers need to be reparented to the new surface to make this
+        // transparent to the app.
+        // If the children are detached, we don't want to reparent them to the new surface.
+        // Instead let the children get removed when the old surface is deleted.
+        if (mSurfaceController != null && mPendingDestroySurface != null && !mChildrenDetached
+                && (mWin.mActivityRecord == null || !mWin.mActivityRecord.isRelaunching())) {
+            mPostDrawTransaction.reparentChildren(
+                    mPendingDestroySurface.getClientViewRootSurface(),
+                    mSurfaceController.mSurfaceControl).apply();
         }
 
         destroyDeferredSurfaceLocked();
         mDestroyPreservedSurfaceUponRedraw = false;
     }
 
-    void markPreservedSurfaceForDestroy() {
+    private void markPreservedSurfaceForDestroy() {
         if (mDestroyPreservedSurfaceUponRedraw
                 && !mService.mDestroyPreservedSurface.contains(mWin)) {
             mService.mDestroyPreservedSurface.add(mWin);
@@ -1363,9 +1363,13 @@
         if (mPendingDestroySurface != null && mDestroyPreservedSurfaceUponRedraw) {
             final SurfaceControl pendingSurfaceControl = mPendingDestroySurface.mSurfaceControl;
             mPostDrawTransaction.reparent(pendingSurfaceControl, null);
-            mPostDrawTransaction.reparentChildren(
-                mPendingDestroySurface.getClientViewRootSurface(),
-                mSurfaceController.mSurfaceControl);
+            // If the children are detached, we don't want to reparent them to the new surface.
+            // Instead let the children get removed when the old surface is deleted.
+            if (!mChildrenDetached) {
+                mPostDrawTransaction.reparentChildren(
+                        mPendingDestroySurface.getClientViewRootSurface(),
+                        mSurfaceController.mSurfaceControl);
+            }
         }
 
         SurfaceControl.mergeToGlobalTransaction(mPostDrawTransaction);
@@ -1593,6 +1597,12 @@
             mSurfaceController.detachChildren();
         }
         mChildrenDetached = true;
+        // If the children are detached, it means the app is exiting. We don't want to tear the
+        // content down too early, otherwise we could end up with a flicker. By preserving the
+        // current surface, we ensure the content remains on screen until the window is completely
+        // removed. It also ensures that the old surface is cleaned up when started again since it
+        // forces mSurfaceController to be set to null.
+        preserveSurfaceLocked();
     }
 
     void setOffsetPositionForStackResize(boolean offsetPositionForStackResize) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java
index 71e79b3..56727e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java
@@ -16,34 +16,23 @@
 package com.android.server.location;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
 
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
+import android.app.ActivityManagerInternal;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.os.Handler;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.server.LocalServices;
 import com.android.server.location.UserInfoHelper.UserListener;
 
 import org.junit.After;
@@ -51,16 +40,18 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-
-import java.util.ArrayList;
-import java.util.List;
 
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class UserInfoHelperTest {
 
+    private static class TestUserInfoHelper extends UserInfoHelper {
+        TestUserInfoHelper(Context context) {
+            super(context);
+        }
+    }
+
     private static final int USER1_ID = 1;
     private static final int USER1_MANAGED_ID = 11;
     private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
@@ -70,69 +61,30 @@
 
     @Mock private Context mContext;
     @Mock private UserManager mUserManager;
+    @Mock private ActivityManagerInternal mActivityManagerInternal;
 
-    private StaticMockitoSession mMockingSession;
-    private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
-
-    private UserInfoHelper mHelper;
+    private TestUserInfoHelper mHelper;
 
     @Before
     public void setUp() {
-        mMockingSession = mockitoSession()
-                .initMocks(this)
-                .spyStatic(ActivityManager.class)
-                .strictness(Strictness.WARN)
-                .startMocking();
+        initMocks(this);
 
+        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal);
         doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
-        doAnswer(invocation -> {
-            mBroadcastReceivers.add(invocation.getArgument(0));
-            return null;
-        }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any(
-                UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class));
-        doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID);
-        doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID);
-        doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent(
-                USER1_MANAGED_ID);
-        doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent(
-                USER2_MANAGED_ID);
 
-        doReturn(USER1_ID).when(ActivityManager::getCurrentUser);
+        doReturn(USER1_PROFILES).when(mUserManager).getEnabledProfileIds(USER1_ID);
+        doReturn(USER2_PROFILES).when(mUserManager).getEnabledProfileIds(USER2_ID);
+        doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER1_ID);
+        doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER1_MANAGED_ID);
+        doReturn(USER1_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds();
 
-        mHelper = new UserInfoHelper(mContext);
+        mHelper = new TestUserInfoHelper(mContext);
         mHelper.onSystemReady();
     }
 
     @After
     public void tearDown() {
-        if (mMockingSession != null) {
-            mMockingSession.finishMocking();
-        }
-    }
-
-    private void switchUser(int userId) {
-        doReturn(userId).when(ActivityManager::getCurrentUser);
-        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE,
-                userId);
-        for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
-            broadcastReceiver.onReceive(mContext, intent);
-        }
-    }
-
-    private void startUser(int userId) {
-        Intent intent = new Intent(Intent.ACTION_USER_STARTED).putExtra(Intent.EXTRA_USER_HANDLE,
-                userId);
-        for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
-            broadcastReceiver.onReceive(mContext, intent);
-        }
-    }
-
-    private void stopUser(int userId) {
-        Intent intent = new Intent(Intent.ACTION_USER_STOPPED).putExtra(Intent.EXTRA_USER_HANDLE,
-                userId);
-        for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
-            broadcastReceiver.onReceive(mContext, intent);
-        }
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
     }
 
     @Test
@@ -140,16 +92,21 @@
         UserListener listener = mock(UserListener.class);
         mHelper.addListener(listener);
 
-        switchUser(USER1_ID);
-        verify(listener, never()).onUserChanged(anyInt(), anyInt());
+        mHelper.dispatchOnCurrentUserChanged(USER1_ID, USER2_ID);
+        verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener, times(1)).onUserChanged(USER1_MANAGED_ID,
+                UserListener.CURRENT_USER_CHANGED);
+        verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener, times(1)).onUserChanged(USER2_MANAGED_ID,
+                UserListener.CURRENT_USER_CHANGED);
 
-        switchUser(USER2_ID);
-        verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED);
-        verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED);
-
-        switchUser(USER1_ID);
-        verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED);
-        verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED);
+        mHelper.dispatchOnCurrentUserChanged(USER2_ID, USER1_ID);
+        verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener, times(2)).onUserChanged(USER2_MANAGED_ID,
+                UserListener.CURRENT_USER_CHANGED);
+        verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener, times(2)).onUserChanged(USER1_MANAGED_ID,
+                UserListener.CURRENT_USER_CHANGED);
     }
 
     @Test
@@ -157,11 +114,11 @@
         UserListener listener = mock(UserListener.class);
         mHelper.addListener(listener);
 
-        startUser(USER1_ID);
+        mHelper.dispatchOnUserStarted(USER1_ID);
         verify(listener).onUserChanged(USER1_ID, UserListener.USER_STARTED);
 
-        startUser(USER2_ID);
-        verify(listener).onUserChanged(USER2_ID, UserListener.USER_STARTED);
+        mHelper.dispatchOnUserStarted(USER1_MANAGED_ID);
+        verify(listener).onUserChanged(USER1_MANAGED_ID, UserListener.USER_STARTED);
     }
 
     @Test
@@ -169,24 +126,22 @@
         UserListener listener = mock(UserListener.class);
         mHelper.addListener(listener);
 
-        stopUser(USER1_ID);
-        verify(listener).onUserChanged(USER1_ID, UserListener.USER_STOPPED);
-
-        stopUser(USER2_ID);
+        mHelper.dispatchOnUserStopped(USER2_ID);
         verify(listener).onUserChanged(USER2_ID, UserListener.USER_STOPPED);
+
+        mHelper.dispatchOnUserStopped(USER2_MANAGED_ID);
+        verify(listener).onUserChanged(USER2_MANAGED_ID, UserListener.USER_STOPPED);
     }
 
     @Test
     public void testCurrentUserIds() {
         assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES);
 
-        switchUser(USER2_ID);
+        doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_ID);
+        doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_MANAGED_ID);
+        doReturn(USER2_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds();
 
         assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER2_PROFILES);
-
-        switchUser(USER1_ID);
-
-        assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES);
     }
 
     @Test
@@ -196,7 +151,11 @@
         assertThat(mHelper.isCurrentUserId(USER2_ID)).isFalse();
         assertThat(mHelper.isCurrentUserId(USER2_MANAGED_ID)).isFalse();
 
-        switchUser(USER2_ID);
+        doReturn(false).when(mActivityManagerInternal).isCurrentProfile(USER1_ID);
+        doReturn(false).when(mActivityManagerInternal).isCurrentProfile(USER1_MANAGED_ID);
+        doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_ID);
+        doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_MANAGED_ID);
+        doReturn(USER2_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds();
 
         assertThat(mHelper.isCurrentUserId(USER1_ID)).isFalse();
         assertThat(mHelper.isCurrentUserId(USER2_ID)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 145e1ec..949bcfe 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -184,6 +184,32 @@
     }
 
     @Test
+    public void handleOnStandby_ScreenOff_NotActiveSource() {
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(
+                mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessage);
+    }
+
+    @Test
+    public void handleOnStandby_ScreenOff_ActiveSource() {
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(
+                mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(standbyMessage);
+    }
+
+    @Test
     public void sendVolumeKeyEvent_up_volumeEnabled() {
         mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
         mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 7af7a23..c34b8e1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -33,6 +33,7 @@
 import android.content.ContextWrapper;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
 import android.os.IPowerManager;
 import android.os.IThermalService;
 import android.os.Looper;
@@ -261,4 +262,89 @@
         mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
         assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isTrue();
     }
+
+    @Test
+    public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
+
+        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.mCallbackReceived).isTrue();
+        assertThat(callback.mVolumeControlEnabled).isTrue();
+    }
+
+    @Test
+    public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
+
+        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.mCallbackReceived).isTrue();
+        assertThat(callback.mVolumeControlEnabled).isFalse();
+    }
+
+    @Test
+    public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
+
+        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.mCallbackReceived).isTrue();
+        assertThat(callback.mVolumeControlEnabled).isTrue();
+    }
+
+    @Test
+    public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
+
+        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback);
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.mCallbackReceived).isTrue();
+        assertThat(callback.mVolumeControlEnabled).isFalse();
+    }
+
+    @Test
+    public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback();
+        VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback();
+
+        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback1);
+        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2);
+
+
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback1.mCallbackReceived).isTrue();
+        assertThat(callback2.mCallbackReceived).isTrue();
+        assertThat(callback1.mVolumeControlEnabled).isTrue();
+        assertThat(callback2.mVolumeControlEnabled).isTrue();
+    }
+
+    private static class VolumeControlFeatureCallback extends
+            IHdmiCecVolumeControlFeatureListener.Stub {
+        boolean mCallbackReceived = false;
+        boolean mVolumeControlEnabled = false;
+
+        @Override
+        public void onHdmiCecVolumeControlFeature(boolean enabled) throws RemoteException {
+            this.mCallbackReceived = true;
+            this.mVolumeControlEnabled = enabled;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index 8774ab0..f4c5506 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -26,7 +26,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.om.OverlayInfo;
-import android.os.OverlayablePolicy;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -205,138 +204,4 @@
         impl.setEnabled(OVERLAY, true, USER);
         assertEquals(0, listener.count);
     }
-
-    @Test
-    public void testConfigurator() {
-        mOverlayableConfigurator = "actor";
-        mOverlayableConfiguratorTargets = new String[]{TARGET};
-        reinitializeImpl();
-
-        installNewPackage(target("actor").setCertificate("one"), USER);
-        installNewPackage(target(TARGET).addOverlayable("TestResources").setCertificate("two"),
-                USER);
-
-        DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
-                .setCertificate("one");
-        installNewPackage(overlay, USER);
-
-        DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(OverlayablePolicy.ACTOR_SIGNATURE,
-                idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-    }
-
-    @Test
-    public void testConfiguratorWithoutOverlayable() {
-        mOverlayableConfigurator = "actor";
-        mOverlayableConfiguratorTargets = new String[]{TARGET};
-        reinitializeImpl();
-
-        installNewPackage(target("actor").setCertificate("one"), USER);
-        installNewPackage(target(TARGET).setCertificate("two"), USER);
-
-        DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET).setCertificate("one");
-        installNewPackage(overlay, USER);
-
-        DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(OverlayablePolicy.ACTOR_SIGNATURE,
-                idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-    }
-
-    @Test
-    public void testConfiguratorDifferentTargets() {
-        // The target package is not listed in the configurator target list, so the actor policy
-        // should not be granted.
-        mOverlayableConfigurator = "actor";
-        mOverlayableConfiguratorTargets = new String[]{"somethingElse"};
-        reinitializeImpl();
-
-        installNewPackage(target("actor").setCertificate("one"), USER);
-        installNewPackage(target(TARGET).setCertificate("two"), USER);
-
-        DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET).setCertificate("one");
-        installNewPackage(overlay, USER);
-
-        DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-    }
-
-    @Test
-    public void testConfiguratorDifferentSignatures() {
-        mOverlayableConfigurator = "actor";
-        mOverlayableConfiguratorTargets = new String[]{TARGET};
-        reinitializeImpl();
-
-        installNewPackage(target("actor").setCertificate("one"), USER);
-        installNewPackage(target(TARGET).addOverlayable("TestResources").setCertificate("two"),
-                USER);
-
-        DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
-                .setCertificate("two");
-        installNewPackage(overlay, USER);
-
-        DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-    }
-
-    @Test
-    public void testConfiguratorWithoutOverlayableDifferentSignatures() {
-        mOverlayableConfigurator = "actor";
-        mOverlayableConfiguratorTargets = new String[]{TARGET};
-        reinitializeImpl();
-
-        installNewPackage(target("actor").setCertificate("one"), USER);
-        installNewPackage(target(TARGET).setCertificate("two"), USER);
-
-        DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET).setCertificate("two");
-        installNewPackage(overlay, USER);
-
-        DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-    }
-
-    @Test
-    public void testConfiguratorChanges() {
-        mOverlayableConfigurator = "actor";
-        mOverlayableConfiguratorTargets = new String[]{TARGET};
-        reinitializeImpl();
-
-        installNewPackage(target("actor").setCertificate("one"), USER);
-        installNewPackage(target(TARGET).addOverlayable("TestResources").setCertificate("two"),
-                USER);
-
-        DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
-                .setCertificate("one");
-        installNewPackage(overlay, USER);
-
-        DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(OverlayablePolicy.ACTOR_SIGNATURE,
-                idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-
-        // Change the configurator to a different package. The overlay should still be granted the
-        // actor policy.
-        mOverlayableConfigurator = "differentActor";
-        OverlayManagerServiceImpl impl = reinitializeImpl();
-        impl.updateOverlaysForUser(USER);
-
-        idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(OverlayablePolicy.ACTOR_SIGNATURE,
-                idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-
-        // Reset the setting persisting that the overlay once fulfilled the actor policy implicitly
-        // through the configurator. The overlay should lose the actor policy.
-        impl = reinitializeImpl();
-        getSettings().setHasConfiguratorActorPolicy(OVERLAY, USER, false);
-        impl.updateOverlaysForUser(USER);
-
-        idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath);
-        assertNotNull(idmap);
-        assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 52a5890..733310b 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -52,9 +52,6 @@
     private DummyPackageManagerHelper mPackageManager;
     private DummyIdmapDaemon mIdmapDaemon;
     private OverlayConfig mOverlayConfig;
-    private OverlayManagerSettings mSettings;
-    String mOverlayableConfigurator;
-    String[] mOverlayableConfiguratorTargets;
 
     @Before
     public void setUp() {
@@ -62,26 +59,20 @@
         mListener = new DummyListener();
         mPackageManager = new DummyPackageManagerHelper(mState);
         mIdmapDaemon = new DummyIdmapDaemon(mState);
-        mSettings = new OverlayManagerSettings();
         mOverlayConfig = mock(OverlayConfig.class);
         when(mOverlayConfig.getPriority(any())).thenReturn(OverlayConfig.DEFAULT_PRIORITY);
         when(mOverlayConfig.isEnabled(any())).thenReturn(false);
         when(mOverlayConfig.isMutable(any())).thenReturn(true);
-        mOverlayableConfigurator = null;
-        mOverlayableConfiguratorTargets = null;
         reinitializeImpl();
     }
 
-    OverlayManagerServiceImpl reinitializeImpl() {
+    void reinitializeImpl() {
         mImpl = new OverlayManagerServiceImpl(mPackageManager,
                 new IdmapManager(mIdmapDaemon, mPackageManager),
-                mSettings,
+                new OverlayManagerSettings(),
                 mOverlayConfig,
                 new String[0],
-                mListener,
-                mOverlayableConfigurator,
-                mOverlayableConfiguratorTargets);
-        return mImpl;
+                mListener);
     }
 
     OverlayManagerServiceImpl getImpl() {
@@ -92,14 +83,6 @@
         return mListener;
     }
 
-    DummyIdmapDaemon getIdmapDaemon() {
-        return mIdmapDaemon;
-    }
-
-    OverlayManagerSettings getSettings() {
-        return mSettings;
-    }
-
     void assertState(@State int expected, final String overlayPackageName, int userId) {
         final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
         if (info == null) {
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index e2cedb5..146f60a 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -367,8 +367,7 @@
                 + "      isEnabled='false'\n"
                 + "      category='dummy-category'\n"
                 + "      isStatic='false'\n"
-                + "      priority='0'"
-                + "      hasConfiguratorActorPolicy='true' />\n"
+                + "      priority='0' />\n"
                 + "</overlays>\n";
         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
 
@@ -381,7 +380,6 @@
         assertEquals(1234, oi.userId);
         assertEquals(STATE_DISABLED, oi.state);
         assertFalse(mSettings.getEnabled("com.dummy.overlay", 1234));
-        assertTrue(mSettings.hasConfiguratorActorPolicy("com.dummy.overlay", 1234));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
index 5412bb5..74b4d12 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
@@ -18,8 +18,8 @@
 
 import android.content.pm.PackageManager
 import android.platform.test.annotations.Presubmit
+import androidx.test.filters.LargeTest
 import com.google.common.truth.Expect
-import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
 import org.junit.Test
 
@@ -52,6 +52,7 @@
         }
     }
 
+    @LargeTest
     @Test
     fun packageInfoEquality() {
         val flags = PackageManager.GET_ACTIVITIES or
@@ -65,7 +66,9 @@
                 PackageManager.GET_SERVICES or
                 PackageManager.GET_SHARED_LIBRARY_FILES or
                 PackageManager.GET_SIGNATURES or
-                PackageManager.GET_SIGNING_CERTIFICATES
+                PackageManager.GET_SIGNING_CERTIFICATES or
+                PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+                PackageManager.MATCH_DIRECT_BOOT_AWARE
         val oldPackageInfo = oldPackages.asSequence().map { oldPackageInfo(it, flags) }
         val newPackageInfo = newPackages.asSequence().map { newPackageInfo(it, flags) }
 
@@ -77,11 +80,79 @@
             } else {
                 "$firstName | $secondName"
             }
-            expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName")
-                    .that(it.first?.dumpToString())
-                    .isEqualTo(it.second?.dumpToString())
+
+            // Main components are asserted independently to separate the failures. Otherwise the
+            // comparison would include every component in one massive string.
+
+            val prefix = "${it.first?.applicationInfo?.sourceDir} $packageName"
+
+            expect.withMessage("$prefix PackageInfo")
+                    .that(it.second?.dumpToString())
+                    .isEqualTo(it.first?.dumpToString())
+
+            expect.withMessage("$prefix ApplicationInfo")
+                    .that(it.second?.applicationInfo?.dumpToString())
+                    .isEqualTo(it.first?.applicationInfo?.dumpToString())
+
+            val firstActivityNames = it.first?.activities?.map { it.name } ?: emptyList()
+            val secondActivityNames = it.second?.activities?.map { it.name } ?: emptyList()
+            expect.withMessage("$prefix activities")
+                    .that(secondActivityNames)
+                    .containsExactlyElementsIn(firstActivityNames)
+                    .inOrder()
+
+            if (!it.first?.activities.isNullOrEmpty() && !it.second?.activities.isNullOrEmpty()) {
+                it.first?.activities?.zip(it.second?.activities!!)?.forEach {
+                    expect.withMessage("$prefix ${it.first.name}")
+                            .that(it.second.dumpToString())
+                            .isEqualTo(it.first.dumpToString())
+                }
+            }
+
+            val firstReceiverNames = it.first?.receivers?.map { it.name } ?: emptyList()
+            val secondReceiverNames = it.second?.receivers?.map { it.name } ?: emptyList()
+            expect.withMessage("$prefix receivers")
+                    .that(secondReceiverNames)
+                    .containsExactlyElementsIn(firstReceiverNames)
+                    .inOrder()
+
+            if (!it.first?.receivers.isNullOrEmpty() && !it.second?.receivers.isNullOrEmpty()) {
+                it.first?.receivers?.zip(it.second?.receivers!!)?.forEach {
+                    expect.withMessage("$prefix ${it.first.name}")
+                            .that(it.second.dumpToString())
+                            .isEqualTo(it.first.dumpToString())
+                }
+            }
+
+            val firstProviderNames = it.first?.providers?.map { it.name } ?: emptyList()
+            val secondProviderNames = it.second?.providers?.map { it.name } ?: emptyList()
+            expect.withMessage("$prefix providers")
+                    .that(secondProviderNames)
+                    .containsExactlyElementsIn(firstProviderNames)
+                    .inOrder()
+
+            if (!it.first?.providers.isNullOrEmpty() && !it.second?.providers.isNullOrEmpty()) {
+                it.first?.providers?.zip(it.second?.providers!!)?.forEach {
+                    expect.withMessage("$prefix ${it.first.name}")
+                            .that(it.second.dumpToString())
+                            .isEqualTo(it.first.dumpToString())
+                }
+            }
+
+            val firstServiceNames = it.first?.services?.map { it.name } ?: emptyList()
+            val secondServiceNames = it.second?.services?.map { it.name } ?: emptyList()
+            expect.withMessage("$prefix services")
+                    .that(secondServiceNames)
+                    .containsExactlyElementsIn(firstServiceNames)
+                    .inOrder()
+
+            if (!it.first?.services.isNullOrEmpty() && !it.second?.services.isNullOrEmpty()) {
+                it.first?.services?.zip(it.second?.services!!)?.forEach {
+                    expect.withMessage("$prefix ${it.first.name}")
+                            .that(it.second.dumpToString())
+                            .isEqualTo(it.first.dumpToString())
+                }
+            }
         }
     }
 }
-
-
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index 0f028f0..420ff19 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
+import android.content.pm.ComponentInfo
 import android.content.pm.ConfigurationInfo
 import android.content.pm.FeatureInfo
 import android.content.pm.InstrumentationInfo
@@ -27,6 +28,8 @@
 import android.content.pm.PackageUserState
 import android.content.pm.PermissionInfo
 import android.content.pm.ProviderInfo
+import android.content.pm.ServiceInfo
+import android.os.Bundle
 import android.os.Debug
 import android.os.Environment
 import android.util.SparseArray
@@ -38,8 +41,10 @@
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import org.junit.BeforeClass
-import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.mock
 import java.io.File
 
@@ -47,7 +52,7 @@
 
     companion object {
 
-        private const val VERIFY_ALL_APKS = false
+        private const val VERIFY_ALL_APKS = true
 
         /** For auditing memory usage differences */
         private const val DUMP_HPROF_TO_EXTERNAL = false
@@ -81,10 +86,14 @@
                             .filter { file -> file.name.endsWith(".apk") }
                             .toList()
                 }
+                .distinct()
 
         private val dummyUserState = mock(PackageUserState::class.java).apply {
             installed = true
-            Mockito.`when`(isAvailable(anyInt())).thenReturn(true)
+            whenever(isAvailable(anyInt())) { true }
+            whenever(isMatch(any<ComponentInfo>(), anyInt())) { true }
+            whenever(isMatch(anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
+                    anyString(), anyInt())) { true }
         }
 
         lateinit var oldPackages: List<PackageParser.Package>
@@ -145,6 +154,7 @@
         private fun mockPkgSetting(aPkg: AndroidPackage) = mockThrowOnUnmocked<PackageSetting> {
             this.pkg = aPkg
             whenever(pkgState) { PackageStateUnserialized() }
+            whenever(readUserState(anyInt())) { dummyUserState }
         }
     }
 
@@ -156,19 +166,10 @@
     // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import
     // the R.attr constant instead of referencing the field in an attempt to fix the error.
 
-    /**
-     * Known exclusions:
-     *   - [ApplicationInfo.credentialProtectedDataDir]
-     *   - [ApplicationInfo.dataDir]
-     *   - [ApplicationInfo.deviceProtectedDataDir]
-     *   - [ApplicationInfo.processName]
-     *   - [ApplicationInfo.publicSourceDir]
-     *   - [ApplicationInfo.scanPublicSourceDir]
-     *   - [ApplicationInfo.scanSourceDir]
-     *   - [ApplicationInfo.sourceDir]
-     * These attributes used to be assigned post-package-parsing as part of another component,
-     * but are now adjusted directly inside [PackageImpl].
-     */
+    // It's difficult to comment out a line in a triple quoted string, so this is used instead
+    // to ignore specific fields. A comment is required to explain why a field was ignored.
+    private fun Any?.ignored(comment: String): String = "IGNORED"
+
     protected fun ApplicationInfo.dumpToString() = """
             appComponentFactory=${this.appComponentFactory}
             backupAgentName=${this.backupAgentName}
@@ -179,22 +180,31 @@
             compatibleWidthLimitDp=${this.compatibleWidthLimitDp}
             compileSdkVersion=${this.compileSdkVersion}
             compileSdkVersionCodename=${this.compileSdkVersionCodename}
+            credentialProtectedDataDir=${this.credentialProtectedDataDir
+            .ignored("Deferred pre-R, but assigned immediately in R")}
+            crossProfile=${this.crossProfile.ignored("Added in R")}
+            dataDir=${this.dataDir.ignored("Deferred pre-R, but assigned immediately in R")}
             descriptionRes=${this.descriptionRes}
+            deviceProtectedDataDir=${this.deviceProtectedDataDir
+            .ignored("Deferred pre-R, but assigned immediately in R")}
             enabled=${this.enabled}
             enabledSetting=${this.enabledSetting}
             flags=${Integer.toBinaryString(this.flags)}
             fullBackupContent=${this.fullBackupContent}
+            gwpAsanMode=${this.gwpAsanMode.ignored("Added in R")}
             hiddenUntilInstalled=${this.hiddenUntilInstalled}
             icon=${this.icon}
             iconRes=${this.iconRes}
             installLocation=${this.installLocation}
+            labelRes=${this.labelRes}
             largestWidthLimitDp=${this.largestWidthLimitDp}
             logo=${this.logo}
             longVersionCode=${this.longVersionCode}
+            ${"".ignored("mHiddenApiPolicy is a private field")}
             manageSpaceActivityName=${this.manageSpaceActivityName}
-            maxAspectRatio.compareTo(that.maxAspectRatio)=${this.maxAspectRatio}
-            metaData=${this.metaData}
-            minAspectRatio.compareTo(that.minAspectRatio)=${this.minAspectRatio}
+            maxAspectRatio=${this.maxAspectRatio}
+            metaData=${this.metaData.dumpToString()}
+            minAspectRatio=${this.minAspectRatio}
             minSdkVersion=${this.minSdkVersion}
             name=${this.name}
             nativeLibraryDir=${this.nativeLibraryDir}
@@ -206,18 +216,27 @@
             permission=${this.permission}
             primaryCpuAbi=${this.primaryCpuAbi}
             privateFlags=${Integer.toBinaryString(this.privateFlags)}
+            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
+            publicSourceDir=${this.publicSourceDir
+            .ignored("Deferred pre-R, but assigned immediately in R")}
             requiresSmallestWidthDp=${this.requiresSmallestWidthDp}
             resourceDirs=${this.resourceDirs?.contentToString()}
             roundIconRes=${this.roundIconRes}
-            secondaryCpuAbi=${this.secondaryCpuAbi}
-            secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
+            scanPublicSourceDir=${this.scanPublicSourceDir
+            .ignored("Deferred pre-R, but assigned immediately in R")}
+            scanSourceDir=${this.scanSourceDir
+            .ignored("Deferred pre-R, but assigned immediately in R")}
             seInfo=${this.seInfo}
             seInfoUser=${this.seInfoUser}
+            secondaryCpuAbi=${this.secondaryCpuAbi}
+            secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
             sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()}
             sharedLibraryInfos=${this.sharedLibraryInfos}
             showUserIcon=${this.showUserIcon}
+            sourceDir=${this.sourceDir
+            .ignored("Deferred pre-R, but assigned immediately in R")}
             splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()}
-            splitDependencies=${this.splitDependencies}
+            splitDependencies=${this.splitDependencies.dumpToString()}
             splitNames=${this.splitNames?.contentToString()}
             splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()}
             splitSourceDirs=${this.splitSourceDirs?.contentToString()}
@@ -226,8 +245,8 @@
             targetSdkVersion=${this.targetSdkVersion}
             taskAffinity=${this.taskAffinity}
             theme=${this.theme}
-            uid=${this.uid}
             uiOptions=${this.uiOptions}
+            uid=${this.uid}
             versionCode=${this.versionCode}
             volumeUuid=${this.volumeUuid}
             zygotePreloadName=${this.zygotePreloadName}
@@ -241,19 +260,27 @@
             """.trimIndent()
 
     protected fun InstrumentationInfo.dumpToString() = """
+            banner=${this.banner}
             credentialProtectedDataDir=${this.credentialProtectedDataDir}
             dataDir=${this.dataDir}
             deviceProtectedDataDir=${this.deviceProtectedDataDir}
             functionalTest=${this.functionalTest}
             handleProfiling=${this.handleProfiling}
+            icon=${this.icon}
+            labelRes=${this.labelRes}
+            logo=${this.logo}
+            metaData=${this.metaData}
+            name=${this.name}
             nativeLibraryDir=${this.nativeLibraryDir}
+            nonLocalizedLabel=${this.nonLocalizedLabel}
+            packageName=${this.packageName}
             primaryCpuAbi=${this.primaryCpuAbi}
             publicSourceDir=${this.publicSourceDir}
             secondaryCpuAbi=${this.secondaryCpuAbi}
             secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
+            showUserIcon=${this.showUserIcon}
             sourceDir=${this.sourceDir}
-            splitDependencies=${this.splitDependencies.sequence()
-            .map { it.first to it.second?.contentToString() }.joinToString()}
+            splitDependencies=${this.splitDependencies.dumpToString()}
             splitNames=${this.splitNames?.contentToString()}
             splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()}
             splitSourceDirs=${this.splitSourceDirs?.contentToString()}
@@ -262,25 +289,40 @@
             """.trimIndent()
 
     protected fun ActivityInfo.dumpToString() = """
+            banner=${this.banner}
             colorMode=${this.colorMode}
             configChanges=${this.configChanges}
+            descriptionRes=${this.descriptionRes}
+            directBootAware=${this.directBootAware}
             documentLaunchMode=${this.documentLaunchMode}
+            enabled=${this.enabled}
+            exported=${this.exported}
             flags=${Integer.toBinaryString(this.flags)}
+            icon=${this.icon}
+            labelRes=${this.labelRes}
             launchMode=${this.launchMode}
             launchToken=${this.launchToken}
             lockTaskLaunchMode=${this.lockTaskLaunchMode}
+            logo=${this.logo}
             maxAspectRatio=${this.maxAspectRatio}
             maxRecents=${this.maxRecents}
+            metaData=${this.metaData.dumpToString()}
             minAspectRatio=${this.minAspectRatio}
+            name=${this.name}
+            nonLocalizedLabel=${this.nonLocalizedLabel}
+            packageName=${this.packageName}
             parentActivityName=${this.parentActivityName}
             permission=${this.permission}
-            persistableMode=${this.persistableMode}
-            privateFlags=${Integer.toBinaryString(this.privateFlags)}
+            persistableMode=${this.persistableMode.ignored("Could be dropped pre-R, fixed in R")}
+            privateFlags=${this.privateFlags}
+            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
             requestedVrComponent=${this.requestedVrComponent}
             resizeMode=${this.resizeMode}
             rotationAnimation=${this.rotationAnimation}
             screenOrientation=${this.screenOrientation}
+            showUserIcon=${this.showUserIcon}
             softInputMode=${this.softInputMode}
+            splitName=${this.splitName}
             targetActivity=${this.targetActivity}
             taskAffinity=${this.taskAffinity}
             theme=${this.theme}
@@ -300,30 +342,77 @@
 
     protected fun PermissionInfo.dumpToString() = """
             backgroundPermission=${this.backgroundPermission}
+            banner=${this.banner}
             descriptionRes=${this.descriptionRes}
             flags=${Integer.toBinaryString(this.flags)}
             group=${this.group}
+            icon=${this.icon}
+            labelRes=${this.labelRes}
+            logo=${this.logo}
+            metaData=${this.metaData.dumpToString()}
+            name=${this.name}
             nonLocalizedDescription=${this.nonLocalizedDescription}
+            nonLocalizedLabel=${this.nonLocalizedLabel}
+            packageName=${this.packageName}
             protectionLevel=${this.protectionLevel}
             requestRes=${this.requestRes}
+            showUserIcon=${this.showUserIcon}
             """.trimIndent()
 
     protected fun ProviderInfo.dumpToString() = """
+            applicationInfo=${this.applicationInfo.ignored("Already checked")}
             authority=${this.authority}
+            banner=${this.banner}
+            descriptionRes=${this.descriptionRes}
+            directBootAware=${this.directBootAware}
+            enabled=${this.enabled}
+            exported=${this.exported}
             flags=${Integer.toBinaryString(this.flags)}
             forceUriPermissions=${this.forceUriPermissions}
             grantUriPermissions=${this.grantUriPermissions}
+            icon=${this.icon}
             initOrder=${this.initOrder}
             isSyncable=${this.isSyncable}
+            labelRes=${this.labelRes}
+            logo=${this.logo}
+            metaData=${this.metaData.dumpToString()}
             multiprocess=${this.multiprocess}
+            name=${this.name}
+            nonLocalizedLabel=${this.nonLocalizedLabel}
+            packageName=${this.packageName}
             pathPermissions=${this.pathPermissions?.joinToString {
         "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}"
     }}
+            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
             readPermission=${this.readPermission}
+            showUserIcon=${this.showUserIcon}
+            splitName=${this.splitName}
             uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()}
             writePermission=${this.writePermission}
             """.trimIndent()
 
+    protected fun ServiceInfo.dumpToString() = """
+            applicationInfo=${this.applicationInfo.ignored("Already checked")}
+            banner=${this.banner}
+            descriptionRes=${this.descriptionRes}
+            directBootAware=${this.directBootAware}
+            enabled=${this.enabled}
+            exported=${this.exported}
+            flags=${Integer.toBinaryString(this.flags)}
+            icon=${this.icon}
+            labelRes=${this.labelRes}
+            logo=${this.logo}
+            mForegroundServiceType"${this.mForegroundServiceType}
+            metaData=${this.metaData.dumpToString()}
+            name=${this.name}
+            nonLocalizedLabel=${this.nonLocalizedLabel}
+            packageName=${this.packageName}
+            permission=${this.permission}
+            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
+            showUserIcon=${this.showUserIcon}
+            splitName=${this.splitName}
+            """.trimIndent()
+
     protected fun ConfigurationInfo.dumpToString() = """
             reqGlEsVersion=${this.reqGlEsVersion}
             reqInputFeatures=${this.reqInputFeatures}
@@ -333,8 +422,10 @@
             """.trimIndent()
 
     protected fun PackageInfo.dumpToString() = """
-            activities=${this.activities?.joinToString { it.dumpToString() }}
-            applicationInfo=${this.applicationInfo.dumpToString()}
+            activities=${this.activities?.joinToString { it.dumpToString() }
+            .ignored("Checked separately in test")}
+            applicationInfo=${this.applicationInfo.dumpToString()
+            .ignored("Checked separately in test")}
             baseRevisionCode=${this.baseRevisionCode}
             compileSdkVersion=${this.compileSdkVersion}
             compileSdkVersionCodename=${this.compileSdkVersionCodename}
@@ -356,15 +447,18 @@
             overlayTarget=${this.overlayTarget}
             packageName=${this.packageName}
             permissions=${this.permissions?.joinToString { it.dumpToString() }}
-            providers=${this.providers?.joinToString { it.dumpToString() }}
-            receivers=${this.receivers?.joinToString { it.dumpToString() }}
+            providers=${this.providers?.joinToString { it.dumpToString() }
+            .ignored("Checked separately in test")}
+            receivers=${this.receivers?.joinToString { it.dumpToString() }
+            .ignored("Checked separately in test")}
             reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }}
             requestedPermissions=${this.requestedPermissions?.contentToString()}
             requestedPermissionsFlags=${this.requestedPermissionsFlags?.contentToString()}
             requiredAccountType=${this.requiredAccountType}
             requiredForAllUsers=${this.requiredForAllUsers}
             restrictedAccountType=${this.restrictedAccountType}
-            services=${this.services?.contentToString()}
+            services=${this.services?.joinToString { it.dumpToString() }
+            .ignored("Checked separately in test")}
             sharedUserId=${this.sharedUserId}
             sharedUserLabel=${this.sharedUserLabel}
             signatures=${this.signatures?.joinToString { it.toCharsString() }}
@@ -378,11 +472,17 @@
             versionName=${this.versionName}
             """.trimIndent()
 
-    @Suppress("unused")
-    private fun <T> SparseArray<T>.sequence(): Sequence<Pair<Int, T>> {
-        var index = 0
-        return generateSequence {
-            index++.takeIf { it < size() }?.let { keyAt(it) to valueAt(index) }
+    private fun Bundle?.dumpToString() = this?.keySet()?.associateWith { get(it) }?.toString()
+
+    private fun <T> SparseArray<T>?.dumpToString(): String {
+        if (this == null) {
+            return "EMPTY"
         }
+
+        val list = mutableListOf<Pair<Int, T>>()
+        for (index in (0 until size())) {
+            list += keyAt(index) to valueAt(index)
+        }
+        return list.toString()
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
index e1f3913..c9c31bf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
@@ -29,6 +29,10 @@
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -79,6 +83,37 @@
         return r;
     }
 
+    private NotificationRecord getNotificationRecordWithBubble(boolean suppressNotif) {
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_UNSPECIFIED);
+        channel.setShowBadge(/* showBadge */ true);
+        when(mConfig.getNotificationChannel(mPkg, mUid, "a", false)).thenReturn(channel);
+
+        Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+                PendingIntent.getActivity(mContext, 0, new Intent(), 0),
+                        Icon.createWithResource("", 0)).build();
+
+        int flags = metadata.getFlags();
+        if (suppressNotif) {
+            flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+        } else {
+            flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+        }
+        metadata.setFlags(flags);
+
+        final Builder builder = new Builder(getContext())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setDefaults(Notification.DEFAULT_SOUND)
+                .setBubbleMetadata(metadata);
+
+        Notification n = builder.build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
+                mPid, n, mUser, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+        return r;
+    }
+
     //
     // Tests
     //
@@ -154,6 +189,20 @@
     }
 
     @Test
+    public void testHideNotifOverridesYes() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+
+        when(mConfig.badgingEnabled(mUser)).thenReturn(true);
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+        NotificationRecord r = getNotificationRecordWithBubble(/* suppressNotif */ true);
+
+        extractor.process(r);
+
+        assertFalse(r.canShowBadge());
+    }
+
+    @Test
     public void testDndOverridesYes() {
         BadgeExtractor extractor = new BadgeExtractor();
         extractor.setConfig(mConfig);
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index f382fba..30df0d4 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -62,6 +62,7 @@
                   android:resumeWhilePausing="true"/>
         <activity android:name="com.android.server.wm.ScreenDecorWindowTests$TestActivity"
                   android:showWhenLocked="true" android:allowEmbedded="true"/>
+        <activity android:name="com.android.server.wm.ActivityLeakTests$DetectLeakActivity" />
     </application>
 
     <instrumentation
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
new file mode 100644
index 0000000..bd6ac58
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertFalse;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.StrictMode;
+import android.os.strictmode.InstanceCountViolation;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for Activity leaks.
+ *
+ * Build/Install/Run:
+ *     atest WmTests:ActivityLeakTests
+ */
+public class ActivityLeakTests {
+
+    private final Instrumentation mInstrumentation = getInstrumentation();
+    private final Context mContext = mInstrumentation.getTargetContext();
+    private final List<Activity> mStartedActivityList = new ArrayList<>();
+
+    @After
+    public void tearDown() {
+        mInstrumentation.runOnMainSync(() -> {
+            // Reset strict mode.
+            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
+        });
+        for (Activity activity : mStartedActivityList) {
+            if (!activity.isDestroyed()) {
+                activity.finish();
+            }
+        }
+        mStartedActivityList.clear();
+    }
+
+    @Test
+    public void testActivityLeak() {
+        final Bundle intentExtras = new Bundle();
+        intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true);
+        final DetectLeakActivity activity = (DetectLeakActivity) startActivity(
+                DetectLeakActivity.class, 0 /* flags */, intentExtras);
+        mStartedActivityList.add(activity);
+
+        activity.finish();
+
+        assertFalse("Leak found on activity", activity.isLeakedAfterDestroy());
+    }
+
+    @Test
+    public void testActivityLeakForTwoInstances() {
+        final Bundle intentExtras = new Bundle();
+
+        // Launch an activity, then enable strict mode
+        intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true);
+        final DetectLeakActivity activity1 = (DetectLeakActivity) startActivity(
+                DetectLeakActivity.class, 0 /* flags */, intentExtras);
+        mStartedActivityList.add(activity1);
+
+        // Launch second activity instance.
+        intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, false);
+        final DetectLeakActivity activity2 = (DetectLeakActivity) startActivity(
+                DetectLeakActivity.class,
+                FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_DOCUMENT, intentExtras);
+        mStartedActivityList.add(activity2);
+
+        // Destroy the activity
+        activity1.finish();
+        assertFalse("Leak found on activity 1", activity1.isLeakedAfterDestroy());
+
+        activity2.finish();
+        assertFalse("Leak found on activity 2", activity2.isLeakedAfterDestroy());
+    }
+
+    private Activity startActivity(Class<?> cls, int flags, Bundle extras) {
+        final Intent intent = new Intent(mContext, cls);
+        intent.addFlags(flags | FLAG_ACTIVITY_NEW_TASK);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        return mInstrumentation.startActivitySync(intent);
+    }
+
+    public static class DetectLeakActivity extends Activity {
+
+        private static final String TAG = "DetectLeakActivity";
+
+        public static final String ENABLE_STRICT_MODE = "enable_strict_mode";
+
+        private volatile boolean mWasDestroyed;
+        private volatile boolean mIsLeaked;
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (getIntent().getBooleanExtra(ENABLE_STRICT_MODE, false)) {
+                enableStrictMode();
+            }
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            getWindow().getDecorView().post(() -> {
+                synchronized (this) {
+                    mWasDestroyed = true;
+                    notifyAll();
+                }
+            });
+        }
+
+        public boolean isLeakedAfterDestroy() {
+            synchronized (this) {
+                while (!mWasDestroyed && !mIsLeaked) {
+                    try {
+                        wait(5000 /* timeoutMs */);
+                    } catch (InterruptedException ignored) {
+                    }
+                }
+            }
+            return mIsLeaked;
+        }
+
+        private void enableStrictMode() {
+            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+                    .detectActivityLeaks()
+                    .penaltyLog()
+                    .penaltyListener(Runnable::run, violation -> {
+                        if (!(violation instanceof InstanceCountViolation)) {
+                            return;
+                        }
+                        synchronized (this) {
+                            mIsLeaked = true;
+                            notifyAll();
+                        }
+                        Log.w(TAG, violation.toString() + ", " + dumpHprofData());
+                    })
+                    .build());
+        }
+
+        private String dumpHprofData() {
+            try {
+                final String fileName = getDataDir().getPath() + "/ActivityLeakHeapDump.hprof";
+                Debug.dumpHprofData(fileName);
+                return "memory dump filename: " + fileName;
+            } catch (Throwable e) {
+                Log.e(TAG, "dumpHprofData failed", e);
+                return "failed to save memory dump";
+            }
+        }
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index cf07221..9621f68 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -73,6 +73,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -230,6 +231,10 @@
         private int mCurUser;
         private boolean mCurUserUnlocked;
         private boolean mCurUserSupported;
+
+        @GuardedBy("this")
+        private boolean mTemporarilyDisabled;
+
         private final boolean mEnableService;
 
         VoiceInteractionManagerServiceStub() {
@@ -316,8 +321,12 @@
                     Settings.Secure.VOICE_INTERACTION_SERVICE, userHandle);
             ComponentName curRecognizer = getCurRecognizer(userHandle);
             VoiceInteractionServiceInfo curInteractorInfo = null;
-            if (DEBUG) Slog.d(TAG, "curInteractorStr=" + curInteractorStr
-                    + " curRecognizer=" + curRecognizer);
+            if (DEBUG) {
+                Slog.d(TAG, "curInteractorStr=" + curInteractorStr
+                        + " curRecognizer=" + curRecognizer
+                        + " mEnableService=" + mEnableService
+                        + " mTemporarilyDisabled=" + mTemporarilyDisabled);
+            }
             if (curInteractorStr == null && curRecognizer != null && mEnableService) {
                 // If there is no interactor setting, that means we are upgrading
                 // from an older platform version.  If the current recognizer is not
@@ -472,10 +481,11 @@
         }
 
         void switchImplementationIfNeededLocked(boolean force) {
-            if (!mCurUserSupported) {
+            if (!mCurUserSupported || mTemporarilyDisabled) {
                 if (DEBUG_USER) {
-                    Slog.d(TAG, "switchImplementationIfNeeded(): skipping on unsuported user "
-                            + mCurUser);
+                    Slog.d(TAG, "switchImplementationIfNeeded(): skipping: force= " + force
+                            + "mCurUserSupported=" + mCurUserSupported
+                            + "mTemporarilyDisabled=" + mTemporarilyDisabled);
                 }
                 if (mImpl != null) {
                     mImpl.shutdownLocked();
@@ -928,6 +938,25 @@
             }
         }
 
+        @Override
+        public void setDisabled(boolean disabled) {
+            enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+            synchronized (this) {
+                if (mTemporarilyDisabled == disabled) {
+                    if (DEBUG) Slog.d(TAG, "setDisabled(): already " + disabled);
+                    return;
+                }
+                Slog.i(TAG, "setDisabled(): changing to " + disabled);
+                final long caller = Binder.clearCallingIdentity();
+                try {
+                    mTemporarilyDisabled = disabled;
+                    switchImplementationIfNeeded(/* force= */ false);
+                } finally {
+                    Binder.restoreCallingIdentity(caller);
+                }
+            }
+        }
+
         //----------------- Model management APIs --------------------------------//
 
         @Override
@@ -1378,6 +1407,7 @@
             synchronized (this) {
                 pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)");
                 pw.println("  mEnableService: " + mEnableService);
+                pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserUnlocked: " + mCurUserUnlocked);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
index 3f4ddb6..6c355a3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
@@ -52,6 +52,8 @@
                 return requestShow(pw);
             case "hide":
                 return requestHide(pw);
+            case "disable":
+                return requestDisable(pw);
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -69,6 +71,8 @@
             pw.println("");
             pw.println("  hide");
             pw.println("    Hides the current session");
+            pw.println("  disable [true|false]");
+            pw.println("    Temporarily disable (when true) service");
             pw.println("");
         }
     }
@@ -127,6 +131,17 @@
         return 0;
     }
 
+    private int requestDisable(PrintWriter pw) {
+        boolean disabled = Boolean.parseBoolean(getNextArgRequired());
+        Slog.i(TAG, "requestDisable(): " + disabled);
+        try {
+            mService.setDisabled(disabled);
+        } catch (Exception e) {
+            return handleError(pw, "requestDisable()", e);
+        }
+        return 0;
+    }
+
     private static int handleError(PrintWriter pw, String message, Exception e) {
         Slog.e(TAG,  "error calling " + message, e);
         pw.printf("Error calling %s: %s\n", message, e);
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 752707e..d366efe 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -594,6 +594,7 @@
         SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo();
         if (etwsInfo != null) {
             cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
+            cv.put(CellBroadcasts.ETWS_IS_PRIMARY, etwsInfo.isPrimary());
         }
 
         SmsCbCmasInfo cmasInfo = getCmasWarningInfo();
@@ -667,9 +668,12 @@
 
         SmsCbEtwsInfo etwsInfo;
         int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE);
-        if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
+        int etwsIsPrimaryColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_IS_PRIMARY);
+        if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)
+                && etwsIsPrimaryColumn != -1 && !cursor.isNull(etwsIsPrimaryColumn)) {
             int warningType = cursor.getInt(etwsWarningTypeColumn);
-            etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
+            boolean isPrimary = cursor.getInt(etwsIsPrimaryColumn) != 0;
+            etwsInfo = new SmsCbEtwsInfo(warningType, false, false, isPrimary, null);
         } else {
             etwsInfo = null;
         }
diff --git a/tests/RollbackTest/MultiUserRollbackTest.xml b/tests/RollbackTest/MultiUserRollbackTest.xml
index ba86c3f..2f62af1 100644
--- a/tests/RollbackTest/MultiUserRollbackTest.xml
+++ b/tests/RollbackTest/MultiUserRollbackTest.xml
@@ -15,6 +15,12 @@
 -->
 <configuration description="Runs rollback tests for multiple users">
     <option name="test-suite-tag" value="MultiUserRollbackTest" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" />
     </test>
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index a4c81d5..42b886f 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -40,23 +40,18 @@
     private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
     private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
 
-    private void cleanUp() throws Exception {
-        getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A");
-        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
-    }
-
     @After
     public void tearDown() throws Exception {
-        cleanUp();
         removeSecondaryUserIfNecessary();
+        runPhaseForUsers("cleanUp", mOriginalUserId);
     }
 
     @Before
     public void setup() throws Exception {
-        cleanUp();
         mOriginalUserId = getDevice().getCurrentUser();
         createAndStartSecondaryUser();
         installPackage("RollbackTest.apk", "--user all");
+        runPhaseForUsers("cleanUp", mOriginalUserId);
     }
 
     @Test
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
index 57c52f9..61d7c76 100644
--- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
@@ -59,12 +59,14 @@
 
     @Before
     public void setUp() throws Exception {
+        runPhase("cleanUp");
         mLogger.start(getDevice());
     }
 
     @After
     public void tearDown() throws Exception {
         mLogger.stop();
+        runPhase("cleanUp");
     }
 
     /**
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
index 400bb04..8641f4d 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
@@ -22,6 +22,7 @@
 
 import android.Manifest;
 import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
 
 import com.android.cts.install.lib.Install;
 import com.android.cts.install.lib.InstallUtils;
@@ -54,6 +55,17 @@
     }
 
     @Test
+    public void cleanUp() {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        assertThat(rm.getAvailableRollbacks()).isEmpty();
+        assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty();
+    }
+
+    @Test
     public void testBasic() throws Exception {
         new RollbackTest().testBasic();
     }
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 8fb59c7..42b0c60 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -19,6 +19,8 @@
 import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
 import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.Manifest;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -91,14 +93,23 @@
     }
 
     @Test
+    public void cleanUp() {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        assertThat(rm.getAvailableRollbacks()).isEmpty();
+        assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty();
+        uninstallNetworkStackPackage();
+    }
+
+    @Test
     public void testNetworkFailedRollback_Phase1() throws Exception {
         // Remove available rollbacks and uninstall NetworkStack on /data/
         RollbackManager rm = RollbackUtils.getRollbackManager();
         String networkStack = getNetworkStackPackageName();
 
-        rm.expireRollbackForPackage(networkStack);
-        uninstallNetworkStackPackage();
-
         assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                 networkStack)).isNull();
 
@@ -153,9 +164,6 @@
         RollbackManager rm = RollbackUtils.getRollbackManager();
         String networkStack = getNetworkStackPackageName();
 
-        rm.expireRollbackForPackage(networkStack);
-        uninstallNetworkStackPackage();
-
         assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                 networkStack)).isNull();
 
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 48b5bed..dd08771 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -48,6 +48,8 @@
 import com.android.cts.rollback.lib.RollbackBroadcastReceiver;
 import com.android.cts.rollback.lib.RollbackUtils;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -81,6 +83,21 @@
                         pri -> packageName.equals(pri.getPackageName())));
     }
 
+    @Before
+    @After
+    public void cleanUp() {
+        try {
+            InstallUtils.adoptShellPermissionIdentity(Manifest.permission.TEST_MANAGE_ROLLBACKS);
+            RollbackManager rm = RollbackUtils.getRollbackManager();
+            rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                    .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+            rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                    .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        } finally {
+            InstallUtils.dropShellPermissionIdentity();
+        }
+    }
+
     /**
      * Test basic rollbacks.
      */
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 6c9ffe2..00bd4cf 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -360,7 +360,10 @@
         RollbackManager rm = RollbackUtils.getRollbackManager();
         rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
                 .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
-        assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
+        rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        assertThat(rm.getAvailableRollbacks()).isEmpty();
+        assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty();
     }
 
     private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
diff --git a/tests/RollbackTest/StagedRollbackTest.xml b/tests/RollbackTest/StagedRollbackTest.xml
index 2750d37..83fef8e 100644
--- a/tests/RollbackTest/StagedRollbackTest.xml
+++ b/tests/RollbackTest/StagedRollbackTest.xml
@@ -19,6 +19,12 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="RollbackTest.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="com.android.tests.rollback.host.StagedRollbackTest" />
     </test>
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 70c5e72..b841921 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -632,7 +632,6 @@
 
     /**
      * @hide
-     * TODO: makes real freq boundaries
      */
     public boolean is24GHz() {
         return ScanResult.is24GHz(mFrequency);
@@ -640,7 +639,6 @@
 
     /**
      * @hide
-     * TODO: makes real freq boundaries
      */
     @UnsupportedAppUsage
     public boolean is5GHz() {
@@ -648,6 +646,13 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean is6GHz() {
+        return ScanResult.is6GHz(mFrequency);
+    }
+
+    /**
      * Record the MAC address of the WLAN interface
      * @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form
      * @hide