Merge "Optimize stack trace reporting puller for speed." into rvc-dev
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 070b13b..7cbb98e 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -219,7 +219,8 @@
          * duration is unknown.
          */
         public long getDurationMicros() {
-            return mExoPlayerSeekMap.getDurationUs();
+            long durationUs = mExoPlayerSeekMap.getDurationUs();
+            return durationUs != C.TIME_UNSET ? durationUs : UNKNOWN_DURATION;
         }
 
         /**
diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java
index 8bd36a5..8be5c63 100644
--- a/apex/statsd/framework/java/android/util/StatsEvent.java
+++ b/apex/statsd/framework/java/android/util/StatsEvent.java
@@ -26,6 +26,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.Arrays;
+
 /**
  * StatsEvent builds and stores the buffer sent over the statsd socket.
  * This class defines and encapsulates the socket protocol.
@@ -224,7 +226,9 @@
 
     // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
     // See android_util_StatsLog.cpp.
-    private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
+    private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
+
+    private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB
 
     private final int mAtomId;
     private final byte[] mPayload;
@@ -619,6 +623,7 @@
         @NonNull
         public Builder usePooledBuffer() {
             mUsePooledBuffer = true;
+            mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos);
             return this;
         }
 
@@ -694,8 +699,9 @@
         @GuardedBy("sLock")
         private static Buffer sPool;
 
-        private final byte[] mBytes = new byte[MAX_PAYLOAD_SIZE];
+        private byte[] mBytes = new byte[MAX_PUSH_PAYLOAD_SIZE];
         private boolean mOverflow = false;
+        private int mMaxSize = MAX_PULL_PAYLOAD_SIZE;
 
         @NonNull
         private static Buffer obtain() {
@@ -717,15 +723,26 @@
         }
 
         private void release() {
-            synchronized (sLock) {
-                if (null == sPool) {
-                    sPool = this;
+            // Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under.
+            if (mBytes.length <= MAX_PUSH_PAYLOAD_SIZE) {
+                synchronized (sLock) {
+                    if (null == sPool) {
+                        sPool = this;
+                    }
                 }
             }
         }
 
         private void reset() {
             mOverflow = false;
+            mMaxSize = MAX_PULL_PAYLOAD_SIZE;
+        }
+
+        private void setMaxSize(final int maxSize, final int numBytesWritten) {
+            mMaxSize = maxSize;
+            if (numBytesWritten > maxSize) {
+                mOverflow = true;
+            }
         }
 
         private boolean hasOverflowed() {
@@ -740,11 +757,28 @@
          * @return true if space is available, false otherwise.
          **/
         private boolean hasEnoughSpace(final int index, final int numBytes) {
-            final boolean result = index + numBytes < MAX_PAYLOAD_SIZE;
-            if (!result) {
+            final int totalBytesNeeded = index + numBytes;
+
+            if (totalBytesNeeded > mMaxSize) {
                 mOverflow = true;
+                return false;
             }
-            return result;
+
+            // Expand buffer if needed.
+            if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) {
+                int newSize = mBytes.length;
+                do {
+                    newSize *= 2;
+                } while (newSize <= totalBytesNeeded);
+
+                if (newSize > mMaxSize) {
+                    newSize = mMaxSize;
+                }
+
+                mBytes = Arrays.copyOf(mBytes, newSize);
+            }
+
+            return true;
         }
 
         /**
diff --git a/apex/statsd/framework/test/src/android/util/StatsEventTest.java b/apex/statsd/framework/test/src/android/util/StatsEventTest.java
index 7b51155..8d26369 100644
--- a/apex/statsd/framework/test/src/android/util/StatsEventTest.java
+++ b/apex/statsd/framework/test/src/android/util/StatsEventTest.java
@@ -33,6 +33,7 @@
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Random;
 
 /**
  * Internal tests for {@link StatsEvent}.
@@ -644,6 +645,165 @@
         statsEvent.release();
     }
 
+    @Test
+    public void testLargePulledEvent() {
+        final int expectedAtomId = 10_020;
+        byte[] field1 = new byte[10 * 1024];
+        new Random().nextBytes(field1);
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent =
+                StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get())
+                .isEqualTo(3);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong())
+                .isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("Third element is not byte array")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_BYTE_ARRAY);
+
+        final byte[] field1Actual = getByteArrayFromByteBuffer(buffer);
+        assertWithMessage("Incorrect field 1").that(field1Actual).isEqualTo(field1);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testPulledEventOverflow() {
+        final int expectedAtomId = 10_020;
+        byte[] field1 = new byte[50 * 1024];
+        new Random().nextBytes(field1);
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent =
+                StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get())
+                .isEqualTo(3);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong())
+                .isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("Third element is not errors type")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_ERRORS);
+
+        final int errorMask = buffer.getInt();
+
+        assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
+                .that(errorMask)
+                .isEqualTo(StatsEvent.ERROR_OVERFLOW);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testPushedEventOverflow() {
+        final int expectedAtomId = 10_020;
+        byte[] field1 = new byte[10 * 1024];
+        new Random().nextBytes(field1);
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                                              .setAtomId(expectedAtomId)
+                                              .writeByteArray(field1)
+                                              .usePooledBuffer()
+                                              .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get())
+                .isEqualTo(3);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong())
+                .isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("Third element is not errors type")
+                .that(buffer.get())
+                .isEqualTo(StatsEvent.TYPE_ERRORS);
+
+        final int errorMask = buffer.getInt();
+
+        assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
+                .that(errorMask)
+                .isEqualTo(StatsEvent.ERROR_OVERFLOW);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
     private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) {
         final int numBytes = buffer.getInt();
         byte[] bytes = new byte[numBytes];
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/Android.bp b/cmds/idmap2/Android.bp
index ef5c4ce..fb58305 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -168,13 +168,13 @@
     ],
     host_supported: true,
     srcs: [
+        "idmap2/CommandUtils.cpp",
         "idmap2/Create.cpp",
         "idmap2/CreateMultiple.cpp",
         "idmap2/Dump.cpp",
         "idmap2/Lookup.cpp",
         "idmap2/Main.cpp",
         "idmap2/Scan.cpp",
-        "idmap2/Verify.cpp",
     ],
     target: {
         android: {
diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp
similarity index 70%
rename from cmds/idmap2/idmap2/Verify.cpp
rename to cmds/idmap2/idmap2/CommandUtils.cpp
index 9cb67b3..e058cd6e 100644
--- a/cmds/idmap2/idmap2/Verify.cpp
+++ b/cmds/idmap2/idmap2/CommandUtils.cpp
@@ -19,30 +19,19 @@
 #include <string>
 #include <vector>
 
-#include "idmap2/CommandLineOptions.h"
 #include "idmap2/Idmap.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
 
-using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::IdmapHeader;
 using android::idmap2::Result;
 using android::idmap2::Unit;
 
-Result<Unit> Verify(const std::vector<std::string>& args) {
-  SYSTRACE << "Verify " << args;
-  std::string idmap_path;
-
-  const CommandLineOptions opts =
-      CommandLineOptions("idmap2 verify")
-          .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path);
-
-  const auto opts_ok = opts.Parse(args);
-  if (!opts_ok) {
-    return opts_ok.GetError();
-  }
-
+Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
+                    const std::string& overlay_path, uint32_t fulfilled_policies,
+                    bool enforce_overlayable) {
+  SYSTRACE << "Verify " << idmap_path;
   std::ifstream fin(idmap_path);
   const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
   fin.close();
@@ -50,7 +39,8 @@
     return Error("failed to parse idmap header");
   }
 
-  const auto header_ok = header->IsUpToDate();
+  const auto header_ok = header->IsUpToDate(target_path.c_str(), overlay_path.c_str(),
+                                            fulfilled_policies, enforce_overlayable);
   if (!header_ok) {
     return Error(header_ok.GetError(), "idmap not up to date");
   }
diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h
new file mode 100644
index 0000000..99605de
--- /dev/null
+++ b/cmds/idmap2/idmap2/CommandUtils.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_IDMAP2_COMMAND_UTILS_H_
+#define IDMAP2_IDMAP2_COMMAND_UTILS_H_
+
+#include "idmap2/Result.h"
+
+android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path,
+                                                      const std::string& target_path,
+                                                      const std::string& overlay_path,
+                                                      uint32_t fulfilled_policies,
+                                                      bool enforce_overlayable);
+
+#endif  // IDMAP2_IDMAP2_COMMAND_UTILS_H_
diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h
index e626738..69eea8d 100644
--- a/cmds/idmap2/idmap2/Commands.h
+++ b/cmds/idmap2/idmap2/Commands.h
@@ -27,6 +27,5 @@
 android::idmap2::Result<android::idmap2::Unit> Dump(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Lookup(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Scan(const std::vector<std::string>& args);
-android::idmap2::Result<android::idmap2::Unit> Verify(const std::vector<std::string>& args);
 
 #endif  // IDMAP2_IDMAP2_COMMANDS_H_
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
index 4b70acc..abdfaf4 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -26,6 +26,7 @@
 #include "android-base/stringprintf.h"
 #include "idmap2/BinaryStreamVisitor.h"
 #include "idmap2/CommandLineOptions.h"
+#include "idmap2/CommandUtils.h"
 #include "idmap2/FileUtils.h"
 #include "idmap2/Idmap.h"
 #include "idmap2/Policies.h"
@@ -103,7 +104,8 @@
       continue;
     }
 
-    if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}))) {
+    if (!Verify(idmap_path, target_apk_path, overlay_apk_path, fulfilled_policies,
+                !ignore_overlayable)) {
       const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
       if (!overlay_apk) {
         LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp
index a07e793..fb093f0 100644
--- a/cmds/idmap2/idmap2/Main.cpp
+++ b/cmds/idmap2/idmap2/Main.cpp
@@ -53,9 +53,8 @@
 int main(int argc, char** argv) {
   SYSTRACE << "main";
   const NameToFunctionMap commands = {
-      {"create", Create}, {"create-multiple", CreateMultiple},
-      {"dump", Dump},     {"lookup", Lookup},
-      {"scan", Scan},     {"verify", Verify},
+      {"create", Create}, {"create-multiple", CreateMultiple}, {"dump", Dump}, {"lookup", Lookup},
+      {"scan", Scan},
   };
   if (argc <= 1) {
     PrintUsage(commands, std::cerr);
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index da04532..3625045 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -27,8 +27,11 @@
 #include "Commands.h"
 #include "android-base/properties.h"
 #include "idmap2/CommandLineOptions.h"
+#include "idmap2/CommandUtils.h"
 #include "idmap2/FileUtils.h"
 #include "idmap2/Idmap.h"
+#include "idmap2/Policies.h"
+#include "idmap2/PolicyUtils.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
@@ -48,6 +51,7 @@
 using android::idmap2::utils::ExtractOverlayManifestInfo;
 using android::idmap2::utils::FindFiles;
 using android::idmap2::utils::OverlayManifestInfo;
+using android::idmap2::utils::PoliciesToBitmaskResult;
 
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 
@@ -215,7 +219,15 @@
 
   std::stringstream stream;
   for (const auto& overlay : interesting_apks) {
-    if (!Verify(std::vector<std::string>({"--idmap-path", overlay.idmap_path}))) {
+    const auto policy_bitmask = PoliciesToBitmaskResult(overlay.policies);
+    if (!policy_bitmask) {
+      LOG(WARNING) << "failed to create idmap for overlay apk path \"" << overlay.apk_path
+                   << "\": " << policy_bitmask.GetErrorMessage();
+      continue;
+    }
+
+    if (!Verify(overlay.idmap_path, target_apk_path, overlay.apk_path, *policy_bitmask,
+                !overlay.ignore_overlayable)) {
       std::vector<std::string> create_args = {"--target-apk-path",  target_apk_path,
                                               "--overlay-apk-path", overlay.apk_path,
                                               "--idmap-path",       overlay.idmap_path};
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index a93184f..908d966 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -33,16 +33,19 @@
 #include "idmap2/BinaryStreamVisitor.h"
 #include "idmap2/FileUtils.h"
 #include "idmap2/Idmap.h"
+#include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
 #include "idmap2/ZipFile.h"
 #include "utils/String8.h"
 
 using android::IPCThreadState;
+using android::base::StringPrintf;
 using android::binder::Status;
 using android::idmap2::BinaryStreamVisitor;
 using android::idmap2::GetPackageCrc;
 using android::idmap2::Idmap;
 using android::idmap2::IdmapHeader;
+using android::idmap2::ZipFile;
 using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
 using android::idmap2::utils::UidHasWriteAccessToPath;
@@ -66,6 +69,21 @@
   return static_cast<PolicyBitmask>(arg);
 }
 
+Status GetCrc(const std::string& apk_path, uint32_t* out_crc) {
+  const auto overlay_zip = ZipFile::Open(apk_path);
+  if (!overlay_zip) {
+    return error(StringPrintf("failed to open apk %s", apk_path.c_str()));
+  }
+
+  const auto crc = GetPackageCrc(*overlay_zip);
+  if (!crc) {
+    return error(crc.GetErrorMessage());
+  }
+
+  *out_crc = *crc;
+  return ok();
+}
+
 }  // namespace
 
 namespace android::os {
@@ -98,10 +116,9 @@
 }
 
 Status Idmap2Service::verifyIdmap(const std::string& target_apk_path,
-                                  const std::string& overlay_apk_path,
-                                  int32_t fulfilled_policies ATTRIBUTE_UNUSED,
-                                  bool enforce_overlayable ATTRIBUTE_UNUSED,
-                                  int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+                                  const std::string& overlay_apk_path, int32_t fulfilled_policies,
+                                  bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
+                                  bool* _aidl_return) {
   SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path;
   assert(_aidl_return);
   const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
@@ -113,34 +130,38 @@
     return error("failed to parse idmap header");
   }
 
-  if (strcmp(header->GetTargetPath().data(), target_apk_path.data()) != 0) {
-    *_aidl_return = false;
-    return ok();
-  }
-
-  if (target_apk_path != kFrameworkPath) {
-    *_aidl_return = (bool) header->IsUpToDate();
+  uint32_t target_crc;
+  if (target_apk_path == kFrameworkPath && android_crc_) {
+    target_crc = *android_crc_;
   } else {
-    if (!android_crc_) {
-      // Loading the framework zip can take several milliseconds. Cache the crc of the framework
-      // resource APK to reduce repeated work during boot.
-      const auto target_zip = idmap2::ZipFile::Open(target_apk_path);
-      if (!target_zip) {
-        return error(base::StringPrintf("failed to open target %s", target_apk_path.c_str()));
-      }
-
-      const auto target_crc = GetPackageCrc(*target_zip);
-      if (!target_crc) {
-        return error(target_crc.GetErrorMessage());
-      }
-
-      android_crc_ = *target_crc;
+    auto target_crc_status = GetCrc(target_apk_path, &target_crc);
+    if (!target_crc_status.isOk()) {
+      *_aidl_return = false;
+      return target_crc_status;
     }
 
-    *_aidl_return = (bool) header->IsUpToDate(android_crc_.value());
+    // Loading the framework zip can take several milliseconds. Cache the crc of the framework
+    // resource APK to reduce repeated work during boot.
+    if (target_apk_path == kFrameworkPath) {
+      android_crc_ = target_crc;
+    }
   }
 
-  // TODO(b/119328308): Check that the set of fulfilled policies of the overlay has not changed
+  uint32_t overlay_crc;
+  auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc);
+  if (!overlay_crc_status.isOk()) {
+    *_aidl_return = false;
+    return overlay_crc_status;
+  }
+
+  auto up_to_date =
+      header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc,
+                         fulfilled_policies, enforce_overlayable);
+  if (!up_to_date) {
+    *_aidl_return = false;
+    return error(up_to_date.GetErrorMessage());
+  }
+
   return ok();
 }
 
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 77a7b30..8f25b8d 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -117,6 +117,14 @@
     return overlay_crc_;
   }
 
+  inline uint32_t GetFulfilledPolicies() const {
+    return fulfilled_policies_;
+  }
+
+  bool GetEnforceOverlayable() const {
+    return enforce_overlayable_;
+  }
+
   inline StringPiece GetTargetPath() const {
     return StringPiece(target_path_);
   }
@@ -132,8 +140,11 @@
   // Invariant: anytime the idmap data encoding is changed, the idmap version
   // field *must* be incremented. Because of this, we know that if the idmap
   // header is up-to-date the entire file is up-to-date.
-  Result<Unit> IsUpToDate() const;
-  Result<Unit> IsUpToDate(uint32_t target_crc_) const;
+  Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path,
+                          uint32_t fulfilled_policies, bool enforce_overlayable) const;
+  Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc,
+                          uint32_t overlay_crc, uint32_t fulfilled_policies,
+                          bool enforce_overlayable) const;
 
   void accept(Visitor* v) const;
 
@@ -145,6 +156,8 @@
   uint32_t version_;
   uint32_t target_crc_;
   uint32_t overlay_crc_;
+  uint32_t fulfilled_policies_;
+  bool enforce_overlayable_;
   char target_path_[kIdmapStringLength];
   char overlay_path_[kIdmapStringLength];
   std::string debug_info_;
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 362dcb3..255212a 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -66,6 +66,8 @@
   Write32(header.GetVersion());
   Write32(header.GetTargetCrc());
   Write32(header.GetOverlayCrc());
+  Write32(header.GetFulfilledPolicies());
+  Write8(static_cast<uint8_t>(header.GetEnforceOverlayable()));
   WriteString256(header.GetTargetPath());
   WriteString256(header.GetOverlayPath());
   WriteString(header.GetDebugInfo());
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 706b842..0bea217 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -112,14 +112,18 @@
 
 std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
-
+  uint8_t enforce_overlayable;
   if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
       !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
+      !Read32(stream, &idmap_header->fulfilled_policies_) ||
+      !Read8(stream, &enforce_overlayable) ||
       !ReadString256(stream, idmap_header->target_path_) ||
       !ReadString256(stream, idmap_header->overlay_path_)) {
     return nullptr;
   }
 
+  idmap_header->enforce_overlayable_ = static_cast<bool>(enforce_overlayable);
+
   auto debug_str = ReadString(stream);
   if (!debug_str) {
     return nullptr;
@@ -129,21 +133,35 @@
   return std::move(idmap_header);
 }
 
-Result<Unit> IdmapHeader::IsUpToDate() const {
-  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_);
+Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
+                                     uint32_t fulfilled_policies, bool enforce_overlayable) const {
+  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
   if (!target_zip) {
-    return Error("failed to open target %s", GetTargetPath().to_string().c_str());
+    return Error("failed to open target %s", target_path);
   }
 
-  Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
+  const Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
   if (!target_crc) {
     return Error("failed to get target crc");
   }
 
-  return IsUpToDate(*target_crc);
+  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path);
+  if (!overlay_zip) {
+    return Error("failed to overlay target %s", overlay_path);
+  }
+
+  const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
+  if (!overlay_crc) {
+    return Error("failed to get overlay crc");
+  }
+
+  return IsUpToDate(target_path, overlay_path, *target_crc, *overlay_crc, fulfilled_policies,
+                    enforce_overlayable);
 }
 
-Result<Unit> IdmapHeader::IsUpToDate(uint32_t target_crc) const {
+Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
+                                     uint32_t target_crc, uint32_t overlay_crc,
+                                     uint32_t fulfilled_policies, bool enforce_overlayable) const {
   if (magic_ != kIdmapMagic) {
     return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic);
   }
@@ -157,19 +175,30 @@
                  target_crc);
   }
 
-  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_);
-  if (!overlay_zip) {
-    return Error("failed to open overlay %s", GetOverlayPath().to_string().c_str());
-  }
-
-  Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
-  if (!overlay_crc) {
-    return Error("failed to get overlay crc");
-  }
-
-  if (overlay_crc_ != *overlay_crc) {
+  if (overlay_crc_ != overlay_crc) {
     return Error("bad overlay crc: idmap version 0x%08x, file system version 0x%08x", overlay_crc_,
-                 *overlay_crc);
+                 overlay_crc);
+  }
+
+  if (fulfilled_policies_ != fulfilled_policies) {
+    return Error("bad fulfilled policies: idmap version 0x%08x, file system version 0x%08x",
+                 fulfilled_policies, fulfilled_policies_);
+  }
+
+  if (enforce_overlayable != enforce_overlayable_) {
+    return Error("bad enforce overlayable: idmap version %s, file system version %s",
+                 enforce_overlayable ? "true" : "false",
+                 enforce_overlayable_ ? "true" : "false");
+  }
+
+  if (strcmp(target_path, target_path_) != 0) {
+    return Error("bad target path: idmap version %s, file system version %s", target_path,
+                 target_path_);
+  }
+
+  if (strcmp(overlay_path, overlay_path_) != 0) {
+    return Error("bad overlay path: idmap version %s, file system version %s", overlay_path,
+                 overlay_path_);
   }
 
   return Unit{};
@@ -320,6 +349,9 @@
   }
   header->overlay_crc_ = *crc;
 
+  header->fulfilled_policies_ = fulfilled_policies;
+  header->enforce_overlayable_ = enforce_overlayable;
+
   if (target_apk_path.size() > sizeof(header->target_path_)) {
     return Error("target apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(),
                  sizeof(header->target_path_));
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index 751c60c..3f62a2a 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -23,10 +23,12 @@
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
 #include "androidfw/ApkAssets.h"
+#include "idmap2/PolicyUtils.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 
 using android::ApkAssets;
+using android::idmap2::policy::PoliciesToDebugString;
 
 namespace {
 
@@ -39,9 +41,6 @@
 
 namespace android::idmap2 {
 
-// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils
-#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
-
 void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
 }
 
@@ -50,6 +49,9 @@
   print(header.GetVersion(), "version");
   print(header.GetTargetCrc(), "target crc");
   print(header.GetOverlayCrc(), "overlay crc");
+  print(header.GetFulfilledPolicies(), "fulfilled policies: %s",
+        PoliciesToDebugString(header.GetFulfilledPolicies()).c_str());
+  print(static_cast<uint8_t>(header.GetEnforceOverlayable()), "enforce overlayable");
   print(header.GetTargetPath().to_string(), kIdmapStringLength, "target path");
   print(header.GetOverlayPath().to_string(), kIdmapStringLength, "overlay path");
   print("...", StringSizeWhenEncoded(header.GetDebugInfo()), "debug info");
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 44acbca..34589a1 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.
@@ -292,13 +291,6 @@
                                                        const PolicyBitmask& fulfilled_policies,
                                                        bool enforce_overlayable,
                                                        LogInfo& log_info) {
-  if (enforce_overlayable) {
-    log_info.Info(LogMessage() << "fulfilled_policies="
-                               << ConcatPolicies(BitmaskToPolicies(fulfilled_policies))
-                               << " enforce_overlayable="
-                               << (enforce_overlayable ? "true" : "false"));
-  }
-
   AssetManager2 target_asset_manager;
   if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true /* invalidate_caches */,
                                          false /* filter_incompatible_configs*/)) {
diff --git a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
index 4973b76..5bd353a 100644
--- a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
+++ b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
@@ -21,9 +21,12 @@
 #include <string>
 #include <vector>
 
+#include "android-base/stringprintf.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/StringPiece.h"
 
+using android::base::StringPrintf;
+
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
@@ -48,6 +51,29 @@
     {kPolicySystem, PolicyFlags::SYSTEM_PARTITION},
     {kPolicyVendor, PolicyFlags::VENDOR_PARTITION},
 };
+
+inline static std::string PoliciesToDebugString(PolicyBitmask policies) {
+  std::string str;
+  uint32_t remaining = policies;
+  for (auto const& policy : kPolicyStringToFlag) {
+    if ((policies & policy.second) != policy.second) {
+      continue;
+    }
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(policy.first.data());
+    remaining &= ~policy.second;
+  }
+  if (remaining != 0) {
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(StringPrintf("0x%08x", remaining));
+  }
+  return !str.empty() ? str : "none";
+}
+
 }  // namespace android::idmap2::policy
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_POLICIES_H_
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index db4778c..5fea7bc 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -48,6 +48,11 @@
   ASSERT_TRUE(result2);
   const auto idmap2 = std::move(*result2);
 
+  ASSERT_EQ(idmap1->GetHeader()->GetFulfilledPolicies(),
+            idmap2->GetHeader()->GetFulfilledPolicies());
+  ASSERT_EQ(idmap1->GetHeader()->GetEnforceOverlayable(),
+            idmap2->GetHeader()->GetEnforceOverlayable());
+  ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath());
   ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc());
   ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath());
   ASSERT_EQ(idmap1->GetData().size(), 1U);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 87da36c..6fab5e0 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -62,9 +62,11 @@
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
   ASSERT_EQ(header->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(header->GetVersion(), 0x03U);
+  ASSERT_EQ(header->GetVersion(), 0x04U);
   ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
+  ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
+  ASSERT_EQ(header->GetEnforceOverlayable(), true);
   ASSERT_EQ(header->GetTargetPath().to_string(), "targetX.apk");
   ASSERT_EQ(header->GetOverlayPath().to_string(), "overlayX.apk");
   ASSERT_EQ(header->GetDebugInfo(), "debug");
@@ -73,7 +75,7 @@
 TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) {
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
   // overwrite the target path string, including the terminating null, with '.'
-  for (size_t i = 0x10; i < 0x110; i++) {
+  for (size_t i = 0x15; i < 0x115; i++) {
     raw[i] = '.';
   }
   std::istringstream stream(raw);
@@ -82,7 +84,7 @@
 }
 
 TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
-  const size_t offset = 0x21c;
+  const size_t offset = 0x221;
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
                   idmap_raw_data_len - offset);
   std::istringstream stream(raw);
@@ -94,7 +96,7 @@
 }
 
 TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
-  const size_t offset = 0x21c;
+  const size_t offset = 0x221;
   std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
                   idmap_raw_data_len - offset);
   std::istringstream stream(raw);
@@ -128,9 +130,11 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x03U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
+  ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), 0x11);
+  ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true);
   ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "targetX.apk");
   ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlayX.apk");
 
@@ -180,9 +184,11 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x03U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
+  ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
+  ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true);
   ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
 }
@@ -389,7 +395,8 @@
 
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
-  ASSERT_TRUE(header->IsUpToDate());
+  ASSERT_TRUE(header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                 PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // magic: bytes (0x0, 0x03)
   std::string bad_magic_string(stream.str());
@@ -402,7 +409,8 @@
       IdmapHeader::FromBinaryStream(bad_magic_stream);
   ASSERT_THAT(bad_magic_header, NotNull());
   ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic());
-  ASSERT_FALSE(bad_magic_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // version: bytes (0x4, 0x07)
   std::string bad_version_string(stream.str());
@@ -415,7 +423,8 @@
       IdmapHeader::FromBinaryStream(bad_version_stream);
   ASSERT_THAT(bad_version_header, NotNull());
   ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion());
-  ASSERT_FALSE(bad_version_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // target crc: bytes (0x8, 0xb)
   std::string bad_target_crc_string(stream.str());
@@ -428,7 +437,8 @@
       IdmapHeader::FromBinaryStream(bad_target_crc_stream);
   ASSERT_THAT(bad_target_crc_header, NotNull());
   ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
-  ASSERT_FALSE(bad_target_crc_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
   // overlay crc: bytes (0xc, 0xf)
   std::string bad_overlay_crc_string(stream.str());
@@ -441,27 +451,55 @@
       IdmapHeader::FromBinaryStream(bad_overlay_crc_stream);
   ASSERT_THAT(bad_overlay_crc_header, NotNull());
   ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
-  ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
-  // target path: bytes (0x10, 0x10f)
+  // fulfilled policy: bytes (0x10, 0x13)
+  std::string bad_policy_string(stream.str());
+  bad_policy_string[0x10] = '.';
+  bad_policy_string[0x11] = '.';
+  bad_policy_string[0x12] = '.';
+  bad_policy_string[0x13] = '.';
+  std::stringstream bad_policy_stream(bad_policy_string);
+  std::unique_ptr<const IdmapHeader> bad_policy_header =
+      IdmapHeader::FromBinaryStream(bad_policy_stream);
+  ASSERT_THAT(bad_policy_header, NotNull());
+  ASSERT_NE(header->GetFulfilledPolicies(), bad_policy_header->GetFulfilledPolicies());
+  ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                             PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+
+  // enforce overlayable: bytes (0x14)
+  std::string bad_enforce_string(stream.str());
+  bad_enforce_string[0x14] = '\0';
+  std::stringstream bad_enforce_stream(bad_enforce_string);
+  std::unique_ptr<const IdmapHeader> bad_enforce_header =
+      IdmapHeader::FromBinaryStream(bad_enforce_stream);
+  ASSERT_THAT(bad_enforce_header, NotNull());
+  ASSERT_NE(header->GetEnforceOverlayable(), bad_enforce_header->GetEnforceOverlayable());
+  ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                              PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
+
+  // target path: bytes (0x15, 0x114)
   std::string bad_target_path_string(stream.str());
-  bad_target_path_string[0x10] = '\0';
+  bad_target_path_string[0x15] = '\0';
   std::stringstream bad_target_path_stream(bad_target_path_string);
   std::unique_ptr<const IdmapHeader> bad_target_path_header =
       IdmapHeader::FromBinaryStream(bad_target_path_stream);
   ASSERT_THAT(bad_target_path_header, NotNull());
   ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
-  ASSERT_FALSE(bad_target_path_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 
-  // overlay path: bytes (0x110, 0x20f)
+  // overlay path: bytes (0x115, 0x214)
   std::string bad_overlay_path_string(stream.str());
-  bad_overlay_path_string[0x110] = '\0';
+  bad_overlay_path_string[0x115] = '\0';
   std::stringstream bad_overlay_path_stream(bad_overlay_path_string);
   std::unique_ptr<const IdmapHeader> bad_overlay_path_header =
       IdmapHeader::FromBinaryStream(bad_overlay_path_stream);
   ASSERT_THAT(bad_overlay_path_header, NotNull());
   ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath());
-  ASSERT_FALSE(bad_overlay_path_header->IsUpToDate());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(),
+                                            PolicyFlags::PUBLIC, /* enforce_overlayable */ true));
 }
 
 class TestVisitor : public Visitor {
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 5c5c81e..b268d5a 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -43,6 +43,8 @@
         << str << "--------";                                     \
   } while (0)
 
+#define ADDRESS "[0-9a-f]{8}: "
+
 TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
   fclose(stderr);  // silence expected warnings
 
@@ -62,15 +64,16 @@
   RawPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-#define ADDRESS "[0-9a-f]{8}: "
   ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000003  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  version\n", stream.str());
   ASSERT_CONTAINS_REGEX(
       StringPrintf(ADDRESS "%s  target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
       stream.str());
   ASSERT_CONTAINS_REGEX(
       StringPrintf(ADDRESS "%s  overlay crc\n", android::idmap2::TestConstants::OVERLAY_CRC_STRING),
       stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000001  fulfilled policies: public\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      01  enforce overlayable\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  target entry count\n", stream.str());
@@ -83,7 +86,6 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  value: integer/int1\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1\n", stream.str());
-#undef ADDRESS
 }
 
 TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
@@ -99,22 +101,23 @@
   RawPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-  ASSERT_NE(stream.str().find("00000000: 504d4449  magic\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000004: 00000003  version\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000008: 00001234  target crc\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000000c: 00005678  overlay crc\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000021c:       7f  target package id\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000021d:       7f  overlay package id\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000021e: 00000003  target entry count\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000222: 00000003  overlay entry count\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000226: 00000000  string pool index offset\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000022a: 00000000  string pool byte length\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000022e: 7f020000  target id\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000232:       01  type: reference\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("00000233: 7f020000  value\n"), std::string::npos);
-
-  ASSERT_NE(stream.str().find("00000249: 7f020000  overlay id\n"), std::string::npos);
-  ASSERT_NE(stream.str().find("0000024d: 7f020000  target id\n"), std::string::npos);
+  ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000004  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00001234  target crc\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00005678  overlay crc\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000011  fulfilled policies: public|signature\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      01  enforce overlayable\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000003  target entry count\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000003  overlay entry count\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000000  string pool index offset\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000000  string pool byte length\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "      01  type: reference\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  value\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  overlay id\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id\n", stream.str());
 }
 
 }  // namespace android::idmap2
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/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index e899589..b599dcb 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -30,7 +30,7 @@
     0x49, 0x44, 0x4d, 0x50,
 
     // 0x4: version
-    0x03, 0x00, 0x00, 0x00,
+    0x04, 0x00, 0x00, 0x00,
 
     // 0x8: target crc
     0x34, 0x12, 0x00, 0x00,
@@ -38,7 +38,13 @@
     // 0xc: overlay crc
     0x78, 0x56, 0x00, 0x00,
 
-    // 0x10: target path "targetX.apk"
+    // 0x10: fulfilled policies
+    0x11, 0x00, 0x00, 0x00,
+
+    // 0x14: enforce overlayable
+    0x01,
+
+    // 0x15: target path "targetX.apk"
     0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -56,7 +62,7 @@
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
-    // 0x110: overlay path "overlayX.apk"
+    // 0x115: overlay path "overlayX.apk"
     0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -74,7 +80,7 @@
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
-    // 0x210: debug string
+    // 0x215: debug string
     // string length, including terminating null
     0x08, 0x00, 0x00, 0x00,
 
@@ -82,63 +88,63 @@
     0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
 
     // DATA HEADER
-    // 0x21c: target_package_id
+    // 0x221: target_package_id
     0x7f,
 
-    // 0x21d: overlay_package_id
+    // 0x222: overlay_package_id
     0x7f,
 
-    // 0x21e: target_entry_count
+    // 0x223: target_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x222: overlay_entry_count
+    // 0x227: overlay_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x226: string_pool_offset
+    // 0x22b: string_pool_offset
     0x00, 0x00, 0x00, 0x00,
 
-    // 0x22a: string_pool_byte_length
+    // 0x22f: string_pool_byte_length
     0x00, 0x00, 0x00, 0x00,
 
     // TARGET ENTRIES
-    // 0x22e: 0x7f020000
-    0x00, 0x00, 0x02, 0x7f,
-
-    // 0x232: TYPE_REFERENCE
-    0x01,
-
     // 0x233: 0x7f020000
     0x00, 0x00, 0x02, 0x7f,
 
-    // 0x237: 0x7f030000
-    0x00, 0x00, 0x03, 0x7f,
-
-    // 0x23b: TYPE_REFERENCE
+    // 0x237: TYPE_REFERENCE
     0x01,
 
+    // 0x238: 0x7f020000
+    0x00, 0x00, 0x02, 0x7f,
+
     // 0x23c: 0x7f030000
     0x00, 0x00, 0x03, 0x7f,
 
-    // 0x240: 0x7f030002
-    0x02, 0x00, 0x03, 0x7f,
-
-    // 0x244: TYPE_REFERENCE
+    // 0x240: TYPE_REFERENCE
     0x01,
 
-    // 0x245: 0x7f030001
+    // 0x241: 0x7f030000
+    0x00, 0x00, 0x03, 0x7f,
+
+    // 0x245: 0x7f030002
+    0x02, 0x00, 0x03, 0x7f,
+
+    // 0x249: TYPE_REFERENCE
+    0x01,
+
+    // 0x24a: 0x7f030001
     0x01, 0x00, 0x03, 0x7f,
 
     // OVERLAY ENTRIES
-    // 0x249: 0x7f020000 -> 0x7f020000
+    // 0x24e: 0x7f020000 -> 0x7f020000
     0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
 
-    // 0x251: 0x7f030000 -> 0x7f030000
+    // 0x256: 0x7f030000 -> 0x7f030000
     0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
 
-    // 0x259: 0x7f030001 -> 0x7f030002
+    // 0x25e: 0x7f030001 -> 0x7f030002
     0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f};
 
-const unsigned int idmap_raw_data_len = 0x261;
+const unsigned int idmap_raw_data_len = 0x266;
 
 std::string GetTestDataPath();
 
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 61e5eb0..33e7649 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -477,14 +477,15 @@
 
     // Run dumping thread
     const uint64_t start = Nanotime();
-    std::thread worker([&]() {
+    std::thread worker([write_fd = std::move(dumpPipe.writeFd()), service = std::move(service),
+                        this]() mutable {
         // Don't crash the service if writing to a closed pipe (may happen if dumping times out)
         signal(SIGPIPE, sigpipe_handler);
-        status_t err = service->dump(dumpPipe.writeFd().get(), mArgs);
+        status_t err = service->dump(write_fd.get(), this->mArgs);
         if (err != OK) {
             ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err));
         }
-        dumpPipe.writeFd().reset();
+        write_fd.reset();
     });
 
     // Collect dump content
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/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 1b75178..508deac 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -23,6 +23,7 @@
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -61,18 +62,8 @@
         }
 
         mTargets.addAll(getTargets(this, mShortcutType));
-
-        final String selectDialogTitle =
-                getString(R.string.accessibility_select_shortcut_menu_title);
         mTargetAdapter = new ShortcutTargetAdapter(mTargets);
-        mMenuDialog = new AlertDialog.Builder(this)
-                .setTitle(selectDialogTitle)
-                .setAdapter(mTargetAdapter, /* listener= */ null)
-                .setPositiveButton(
-                        getString(R.string.edit_accessibility_shortcut_menu_button),
-                        /* listener= */ null)
-                .setOnDismissListener(dialog -> finish())
-                .create();
+        mMenuDialog = createMenuDialog();
         mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
         mMenuDialog.show();
     }
@@ -154,4 +145,22 @@
         mMenuDialog.getListView().setOnItemClickListener(
                 isEditMenuMode ? this::onTargetChecked : this::onTargetSelected);
     }
+
+    private AlertDialog createMenuDialog() {
+        final String dialogTitle =
+                getString(R.string.accessibility_select_shortcut_menu_title);
+
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+                .setTitle(dialogTitle)
+                .setAdapter(mTargetAdapter, /* listener= */ null)
+                .setOnDismissListener(dialog -> finish());
+
+        if (isUserSetupCompleted(this)) {
+            final String positiveButtonText =
+                    getString(R.string.edit_accessibility_shortcut_menu_button);
+            builder.setPositiveButton(positiveButtonText, /* listener= */ null);
+        }
+
+        return builder.create();
+    }
 }
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 9ee0b0e..4b4e20f 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -156,4 +156,16 @@
 
         return false;
     }
+
+    /**
+     * Indicates whether the current user has completed setup via the setup wizard.
+     * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE}
+     *
+     * @return {@code true} if the setup is completed.
+     */
+    public static boolean isUserSetupCompleted(Context context) {
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, /* def= */ 0, UserHandle.USER_CURRENT)
+                != /* false */ 0;
+    }
 }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index ff34c86..b36c71f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -793,7 +793,6 @@
     private AppPredictor.Callback createAppPredictorCallback(
             ChooserListAdapter chooserListAdapter) {
         return resultList -> {
-            //TODO(arangelov) Take care of edge case when callback called after swiping tabs
             if (isFinishing() || isDestroyed()) {
                 return;
             }
@@ -802,8 +801,6 @@
             }
             if (resultList.isEmpty()) {
                 // APS may be disabled, so try querying targets ourselves.
-                //TODO(arangelov) queryDirectShareTargets indirectly uses mIntents.
-                // Investigate implications for work tab.
                 queryDirectShareTargets(chooserListAdapter, true);
                 return;
             }
@@ -1809,7 +1806,8 @@
         }
     }
 
-    void queryTargetServices(ChooserListAdapter adapter) {
+    @VisibleForTesting
+    protected void queryTargetServices(ChooserListAdapter adapter) {
         mQueriedTargetServicesTimeMs = System.currentTimeMillis();
 
         Context selectedProfileContext = createContextAsUser(
@@ -1962,7 +1960,8 @@
         return driList;
     }
 
-    private void queryDirectShareTargets(
+    @VisibleForTesting
+    protected void queryDirectShareTargets(
                 ChooserListAdapter adapter, boolean skipAppPredictionService) {
         mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
         UserHandle userHandle = adapter.getUserHandle();
@@ -1974,7 +1973,6 @@
             }
         }
         // Default to just querying ShortcutManager if AppPredictor not present.
-        //TODO(arangelov) we're using mIntents here, investicate possible implications on work tab
         final IntentFilter filter = getTargetIntentFilter();
         if (filter == null) {
             return;
@@ -2654,6 +2652,7 @@
                 if (recyclerView.getVisibility() == View.VISIBLE) {
                     int directShareHeight = 0;
                     rowsToShow = Math.min(4, rowsToShow);
+                    boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
                     mLastNumberOfChildren = recyclerView.getChildCount();
                     for (int i = 0, childCount = recyclerView.getChildCount();
                             i < childCount && rowsToShow > 0; i++) {
@@ -2664,6 +2663,9 @@
                         }
                         int height = child.getHeight();
                         offset += height;
+                        if (shouldShowExtraRow) {
+                            offset += height;
+                        }
 
                         if (gridAdapter.getTargetType(
                                 recyclerView.getChildAdapterPosition(child))
@@ -2699,6 +2701,18 @@
     }
 
     /**
+     * If we have a tabbed view and are showing 1 row in the current profile and an empty
+     * state screen in the other profile, to prevent cropping of the empty state screen we show
+     * a second row in the current profile.
+     */
+    private boolean shouldShowExtraRow(int rowsToShow) {
+        return shouldShowTabs()
+                && rowsToShow == 1
+                && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(
+                        mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
+    }
+
+    /**
      * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
      * does not match either the personal or work user handle.
      **/
@@ -3043,10 +3057,6 @@
         currentRootAdapter.updateDirectShareExpansion();
     }
 
-    void prepareIntentForCrossProfileLaunch(Intent intent) {
-        intent.fixUris(UserHandle.myUserId());
-    }
-
     @Override
     protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
         if (shouldShowTabs()) {
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 838d9bc..daacd45 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -159,9 +159,6 @@
     protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver";
     protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser";
 
-    /**
-     * TODO(arangelov): Remove a couple of weeks after work/personal tabs are finalized.
-     */
     @VisibleForTesting
     public static boolean ENABLE_TABBED_VIEW = true;
     private static final String TAB_TAG_PERSONAL = "personal";
@@ -184,6 +181,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 +479,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 +547,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);
     }
@@ -1250,7 +1246,9 @@
         return true;
     }
 
-    void prepareIntentForCrossProfileLaunch(Intent intent) {}
+    private void prepareIntentForCrossProfileLaunch(Intent intent) {
+        intent.fixUris(UserHandle.myUserId());
+    }
 
     private boolean isLaunchingTargetInOtherProfile() {
         return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
@@ -1818,6 +1816,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()) {
@@ -1834,12 +1838,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/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 97f43d27..b1e8ed1 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -423,7 +423,6 @@
     // We assume that at this point we've already filtered out the only intent for a different
     // targetUserId which we're going to use.
     private void addResolveInfo(DisplayResolveInfo dri) {
-        // TODO(arangelov): Is that UserHandle.USER_CURRENT check okay?
         if (dri != null && dri.getResolveInfo() != null
                 && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) {
             if (shouldAddResolveInfo(dri)) {
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-television/themes_device_defaults.xml b/core/res/res/values-television/themes_device_defaults.xml
index cb3d328..d6bdeee 100644
--- a/core/res/res/values-television/themes_device_defaults.xml
+++ b/core/res/res/values-television/themes_device_defaults.xml
@@ -33,5 +33,6 @@
     <style name="Theme.DeviceDefault.Autofill.Light" parent="Theme.DeviceDefault.Autofill"/>
     <style name="Theme.DeviceDefault.Light.Autofill.Save" parent="Theme.DeviceDefault.Autofill.Save"/>
 
-    <style name="Theme.DeviceDefault.Resolver" parent="Theme.Leanback.Resolver" />
+    <style name="Theme.DeviceDefault.ResolverCommon" parent="Theme.Leanback.Resolver" />
+    <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon" />
 </resources>
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..051bf7c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1785,6 +1785,7 @@
   <java-symbol type="string" name="faceunlock_multiple_failures" />
   <java-symbol type="string" name="global_actions" />
   <java-symbol type="string" name="global_action_power_off" />
+  <java-symbol type="string" name="global_action_power_options" />
   <java-symbol type="string" name="global_action_restart" />
   <java-symbol type="string" name="global_actions_airplane_mode_off_status" />
   <java-symbol type="string" name="global_actions_airplane_mode_on_status" />
@@ -2766,6 +2767,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 +3535,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 +4037,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/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index a80725c..9dca912 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -128,6 +128,10 @@
 
         <!-- Toolbar attributes -->
         <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+        <!-- Icon sizes -->
+        <item name="iconfactoryIconSize">@dimen/resolver_icon_size</item>
+        <item name="iconfactoryBadgeSize">@dimen/resolver_badge_size</item>
       </style>
 
     <!-- @hide Special theme for the default system Activity-based Alert dialogs. -->
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/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index dcecb5f..5471768 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -34,9 +34,11 @@
 import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
 import static com.android.internal.app.MatcherUtils.first;
 
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
@@ -1327,7 +1329,6 @@
         assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
     }
 
-    @Ignore // b/148156663
     @Test
     public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
         // enable the work tab feature flag
@@ -1354,8 +1355,10 @@
         // wait for the share sheet to expand
         Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
 
-        onView(first(withText(workResolvedComponentInfos.get(0)
-                .getResolveInfoAt(0).activityInfo.applicationInfo.name)))
+        onView(first(allOf(
+                withText(workResolvedComponentInfos.get(0)
+                        .getResolveInfoAt(0).activityInfo.applicationInfo.name),
+                isDisplayed())))
                 .perform(click());
         waitForIdle();
         assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
@@ -1953,6 +1956,45 @@
         assertThat(activity.getAdapter().getRankedTargetCount(), is(3));
     }
 
+    @Test
+    public void testWorkTab_selectingWorkTabWithPausedWorkProfile_directShareTargetsNotQueried() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        sOverrides.isQuietModeEnabled = true;
+        boolean[] isQueryDirectShareCalledOnWorkProfile = new boolean[] { false };
+        sOverrides.onQueryDirectShareTargets = chooserListAdapter -> {
+            isQueryDirectShareCalledOnWorkProfile[0] =
+                    (chooserListAdapter.getUserHandle().getIdentifier() == 10);
+            return null;
+        };
+        boolean[] isQueryTargetServicesCalledOnWorkProfile = new boolean[] { false };
+        sOverrides.onQueryTargetServices = chooserListAdapter -> {
+            isQueryTargetServicesCalledOnWorkProfile[0] =
+                    (chooserListAdapter.getUserHandle().getIdentifier() == 10);
+            return null;
+        };
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        assertFalse("Direct share targets were queried on a paused work profile",
+                isQueryDirectShareCalledOnWorkProfile[0]);
+        assertFalse("Target services were queried on a paused work profile",
+                isQueryTargetServicesCalledOnWorkProfile[0]);
+    }
+
     private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
         Intent chooserIntent = new Intent();
         chooserIntent.setAction(Intent.ACTION_CHOOSER);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 749b0e5..44a5263 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -205,6 +205,23 @@
         return getApplicationContext();
     }
 
+    @Override
+    protected void queryDirectShareTargets(ChooserListAdapter adapter,
+            boolean skipAppPredictionService) {
+        if (sOverrides.onQueryDirectShareTargets != null) {
+            sOverrides.onQueryDirectShareTargets.apply(adapter);
+        }
+        super.queryDirectShareTargets(adapter, skipAppPredictionService);
+    }
+
+    @Override
+    protected void queryTargetServices(ChooserListAdapter adapter) {
+        if (sOverrides.onQueryTargetServices != null) {
+            sOverrides.onQueryTargetServices.apply(adapter);
+        }
+        super.queryTargetServices(adapter);
+    }
+
     /**
      * We cannot directly mock the activity created since instrumentation creates it.
      * <p>
@@ -214,6 +231,8 @@
         @SuppressWarnings("Since15")
         public Function<PackageManager, PackageManager> createPackageManager;
         public Function<TargetInfo, Boolean> onSafelyStartCallback;
+        public Function<ChooserListAdapter, Void> onQueryDirectShareTargets;
+        public Function<ChooserListAdapter, Void> onQueryTargetServices;
         public ResolverListController resolverListController;
         public ResolverListController workResolverListController;
         public Boolean isVoiceInteraction;
@@ -233,6 +252,8 @@
 
         public void reset() {
             onSafelyStartCallback = null;
+            onQueryDirectShareTargets = null;
+            onQueryTargetServices = null;
             isVoiceInteraction = null;
             createPackageManager = null;
             previewThumbnail = null;
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 8bee1e5..7dc5a8b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -592,7 +592,6 @@
                 TextUtils.equals(initialText, currentText));
     }
 
-    @Ignore // b/148156663
     @Test
     public void testWorkTab_noPersonalApps_canStartWorkApps()
             throws InterruptedException {
@@ -617,8 +616,10 @@
         waitForIdle();
         // wait for the share sheet to expand
         Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
-        onView(first(allOf(withText(workResolvedComponentInfos.get(0)
-                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+        onView(first(allOf(
+                withText(workResolvedComponentInfos.get(0)
+                        .getResolveInfoAt(0).activityInfo.applicationInfo.name),
+                isDisplayed())))
                 .perform(click());
         onView(withId(R.id.button_once))
                 .perform(click());
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/overlaytests/remount/Android.bp b/core/tests/overlaytests/remount/Android.bp
index 5757cfe..4e79a45 100644
--- a/core/tests/overlaytests/remount/Android.bp
+++ b/core/tests/overlaytests/remount/Android.bp
@@ -28,5 +28,6 @@
         ":OverlayRemountedTest_Target",
         ":OverlayRemountedTest_TargetUpgrade",
         ":OverlayRemountedTest_Overlay",
+        ":OverlayRemountedTest_Overlay_SameCert",
     ],
 }
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/RegenerateIdmapTest.java
similarity index 66%
rename from core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java
rename to core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/RegenerateIdmapTest.java
index a465640..2b68015 100644
--- a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/RegenerateIdmapTest.java
@@ -22,7 +22,9 @@
 import org.junit.runner.RunWith;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class PackagedUpgradedTest extends OverlayRemountedTestBase {
+public class RegenerateIdmapTest extends OverlayRemountedTestBase {
+    private static final String OVERLAY_SIGNATURE_APK =
+            "OverlayRemountedTest_Overlay_SameCert.apk";
     private static final String TARGET_UPGRADE_APK = "OverlayRemountedTest_TargetUpgrade.apk";
 
     @Test
@@ -66,4 +68,32 @@
         assertResource(targetReference, "@" + 0x7f0100ff + " -> true");
         assertResource(targetOverlaid, "true");
     }
+
+    @Test
+    public void testIdmapPoliciesChanged() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+                "signature_policy_overlaid");
+
+        mPreparer.pushResourceFile(TARGET_APK, "/product/app/OverlayTarget.apk")
+                .pushResourceFile(OVERLAY_APK, "/product/overlay/TestOverlay.apk")
+                .reboot()
+                .setOverlayEnabled(OVERLAY_PACKAGE, false);
+
+        assertResource(targetResource, "false");
+
+        // The overlay is not signed with the same signature as the target.
+        mPreparer.setOverlayEnabled(OVERLAY_PACKAGE, true);
+        assertResource(targetResource, "false");
+
+        // Replace the overlay with a version of the overlay that is signed with the same signature
+        // as the target.
+        mPreparer.pushResourceFile(OVERLAY_SIGNATURE_APK, "/product/overlay/TestOverlay.apk")
+                .reboot();
+
+        // The idmap should have been recreated with the signature policy fulfilled.
+        assertResource(targetResource, "true");
+
+        mPreparer.setOverlayEnabled(OVERLAY_PACKAGE, false);
+        assertResource(targetResource, "false");
+    }
 }
diff --git a/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
index a1fdbfd..032a0cd 100644
--- a/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
@@ -19,3 +19,9 @@
         "com.android.overlaytest.overlay",
     ],
 }
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_Overlay_SameCert",
+    certificate: ":rro-remounted-test-a",
+    sdk_version: "current",
+}
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
index 675e44f..927d37f 100644
--- a/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
@@ -17,4 +17,5 @@
 
 <resources>
     <bool name="target_overlaid">true</bool>
+    <bool name="signature_policy_overlaid">true</bool>
 </resources>
diff --git a/core/tests/overlaytests/remount/test-apps/Target/Android.bp b/core/tests/overlaytests/remount/test-apps/Target/Android.bp
index 19947b1..e4b4eaa 100644
--- a/core/tests/overlaytests/remount/test-apps/Target/Android.bp
+++ b/core/tests/overlaytests/remount/test-apps/Target/Android.bp
@@ -15,6 +15,7 @@
 android_test_helper_app {
     name: "OverlayRemountedTest_Target",
     sdk_version: "test_current",
+    certificate: ":rro-remounted-test-a",
     apex_available: [
         "com.android.overlaytest.overlaid",
     ],
@@ -23,6 +24,7 @@
 
 android_test_helper_app {
     name: "OverlayRemountedTest_TargetUpgrade",
+    certificate: ":rro-remounted-test-a",
     resource_dirs: ["res_upgrade"],
     sdk_version: "test_current",
 }
diff --git a/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
index 4aa5bce..79c9a67 100644
--- a/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
@@ -20,5 +20,8 @@
         <policy type="public">
             <item type="bool" name="target_overlaid" />
         </policy>
+        <policy type="signature">
+            <item type="bool" name="signature_policy_overlaid" />
+        </policy>
     </overlayable>
 </resources>
diff --git a/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
index 76253a9..64a1683 100644
--- a/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
@@ -23,4 +23,6 @@
     <bool name="target_overlaid">false</bool>
     <public type="bool" name="target_overlaid" id="0x7f010000" />
     <bool name="target_reference">@bool/target_overlaid</bool>
+
+    <bool name="signature_policy_overlaid">false</bool>
 </resources>
diff --git a/core/tests/overlaytests/remount/test-apps/certs/Android.bp b/core/tests/overlaytests/remount/test-apps/certs/Android.bp
new file mode 100644
index 0000000..06114ef
--- /dev/null
+++ b/core/tests/overlaytests/remount/test-apps/certs/Android.bp
@@ -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.
+
+// development/tools/make_key rro-remounted-test-a '/CN=rro_test_a'
+android_app_certificate {
+    name: "rro-remounted-test-a",
+    certificate: "rro-remounted-test-a",
+}
diff --git a/core/tests/overlaytests/remount/test-apps/certs/rro-remounted-test-a.pk8 b/core/tests/overlaytests/remount/test-apps/certs/rro-remounted-test-a.pk8
new file mode 100644
index 0000000..aa6cc97
--- /dev/null
+++ b/core/tests/overlaytests/remount/test-apps/certs/rro-remounted-test-a.pk8
Binary files differ
diff --git a/core/tests/overlaytests/remount/test-apps/certs/rro-remounted-test-a.x509.pem b/core/tests/overlaytests/remount/test-apps/certs/rro-remounted-test-a.x509.pem
new file mode 100644
index 0000000..be491c7
--- /dev/null
+++ b/core/tests/overlaytests/remount/test-apps/certs/rro-remounted-test-a.x509.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCzCCAfOgAwIBAgIUfphI+C6W6V6RomsP7+CW5dO5cGcwDQYJKoZIhvcNAQEL
+BQAwFTETMBEGA1UEAwwKcnJvX3Rlc3RfYTAeFw0yMDA1MTQxNjM4MDBaFw00NzA5
+MzAxNjM4MDBaMBUxEzARBgNVBAMMCnJyb190ZXN0X2EwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCvXM8tcqQFwH7iQG6+8mAx2ADhwbtq+8Rcmiz7wviW
+Yf/eFDRuvZ/ma6lxeVJ7mcbF7A5rinEKdgN5hlW2UlbTmuX5YOiXnX3Y2J5t+8Pi
+aq787IvWxkawwkj0Oy1Hk01Z4w3HTYntYqi36bq4QyNpwh515VqgvEyCHT7IPtQi
+XjfwcTW0thUlSDyDkgxq9NxNEJgaHHOamKkeMCO8CkBWkhlcPXvjcM8DPFmyzDI9
+Czv8IYFZQbcG/N2GPH9hSteMnuC+zyoMio0V/VRctQGlAA8ATsheBkng0zcNRu9Z
+GIavk5AaClmBFTeQx01j3HFSO8UDdDJ5Hk8uDTqecPLpAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSPbIdzSkPbzltj3qIS13LNDiyIiDAfBgNVHSMEGDAWgBSPbIdzSkPbzltj
+3qIS13LNDiyIiDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCH
+QvvMGyMvVJaWMEJwVdUnszdXiAlUtDd/2HpdGOxW6xVVAvveP8hJ71gWFQ7Qs3Mr
+3jxclbC37qVAPiQb8kkD8qUgoQYMC43asif6Jn65OU1QkDRF3bFHP+rZVSPEwtvl
+YMbOzHPOLr7HESwlM7TB6EoZ4oOso++jTYI/OSif1MOKOMbOt4X/DE/PXf81ayFs
+uRjpocBqnLwOourABMcaKbA92jB0LRTtgv5ngOJ3+5P1cTiHktFbnqVWa8/A3uSA
+dNR5dpOUjH+nCHTwPl64b7R70PgDxnoqMs0xI7VtJovXor64OZy9P8WTdurz5V/z
+k2IVSi032bf0aTxamvqV
+-----END CERTIFICATE-----
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/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 2bfc7fc..21be81c 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -41,7 +41,7 @@
 namespace android {
 
 constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const static uint32_t kIdmapCurrentVersion = 0x00000003u;
+constexpr const static uint32_t kIdmapCurrentVersion = 0x00000004u;
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1746,6 +1746,9 @@
   uint32_t target_crc32;
   uint32_t overlay_crc32;
 
+  uint32_t fulfilled_policies;
+  uint8_t enforce_overlayable;
+
   uint8_t target_path[256];
   uint8_t overlay_path[256];
 
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index 62e9866..f1ed592 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 3759ed6..29c5eb6 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
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/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 8ae98ae..23fadac 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -146,6 +146,8 @@
         mPendingAppPrivateCommands.clear();
         if (mSession != null) {
             mSession.release();
+            mIsTuned = false;
+            mIsRecordingStarted = false;
             mSession = null;
         }
     }
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-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-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/controls_detail_dialog.xml b/packages/SystemUI/res/layout/controls_detail_dialog.xml
index d61122f..ee5315a 100644
--- a/packages/SystemUI/res/layout/controls_detail_dialog.xml
+++ b/packages/SystemUI/res/layout/controls_detail_dialog.xml
@@ -42,6 +42,7 @@
         android:layout_height="1dp" />
     <ImageView
         android:id="@+id/control_detail_open_in_app"
+        android:contentDescription="@string/controls_open_app"
         android:src="@drawable/ic_open_in_new"
         android:background="?android:attr/selectableItemBackgroundBorderless"
         android:tint="@color/control_primary_text"
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/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 8a2c101..734199e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -578,6 +578,18 @@
     }
 
     /**
+     * Called when the status bar has become visible or invisible (either permanently or
+     * temporarily).
+     */
+    public void onStatusBarVisibilityChanged(boolean visible) {
+        if (mStackView != null) {
+            // Hide the stack temporarily if the status bar has been made invisible, and the stack
+            // is collapsed. An expanded stack should remain visible until collapsed.
+            mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
+        }
+    }
+
+    /**
      * Sets whether to perform inflation on the same thread as the caller. This method should only
      * be used in tests, not in production.
      */
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..95c8d08 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;
@@ -79,6 +80,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.animation.ExpandedAnimationController;
@@ -88,6 +90,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -133,6 +136,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;
@@ -239,6 +245,9 @@
     /** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
     private boolean mIsGestureInProgress = false;
 
+    /** Whether or not the stack is temporarily invisible off the side of the screen. */
+    private boolean mTemporarilyInvisible = false;
+
     /** Description of current animation controller state. */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Stack view state:");
@@ -261,7 +270,7 @@
     private boolean mShowingDismiss = false;
 
     /** The view to desaturate/darken when magneted to the dismiss target. */
-    private View mDesaturateAndDarkenTargetView;
+    @Nullable private View mDesaturateAndDarkenTargetView;
 
     private LayoutInflater mInflater;
 
@@ -643,6 +652,7 @@
         }
     };
 
+    private View mDismissTargetCircle;
     private ViewGroup mDismissTargetContainer;
     private PhysicsAnimator<View> mDismissTargetAnimator;
     private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
@@ -749,25 +759,32 @@
         mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
 
         final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
-        final View targetView = new DismissCircleView(context);
+        mDismissTargetCircle = new DismissCircleView(context);
         final FrameLayout.LayoutParams newParams =
                 new FrameLayout.LayoutParams(targetSize, targetSize);
-        newParams.gravity = Gravity.CENTER;
-        targetView.setLayoutParams(newParams);
-        mDismissTargetAnimator = PhysicsAnimator.getInstance(targetView);
+        newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+        mDismissTargetCircle.setLayoutParams(newParams);
+        mDismissTargetAnimator = PhysicsAnimator.getInstance(mDismissTargetCircle);
 
         mDismissTargetContainer = new FrameLayout(context);
         mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
                 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.addView(mDismissTargetCircle);
         mDismissTargetContainer.setVisibility(View.INVISIBLE);
+        mDismissTargetContainer.setBackgroundResource(
+                R.drawable.floating_dismiss_gradient_transition);
         addView(mDismissTargetContainer);
 
         // Start translated down so the target springs up.
-        targetView.setTranslationY(
+        mDismissTargetCircle.setTranslationY(
                 getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
 
         final ContentResolver contentResolver = getContext().getContentResolver();
@@ -776,7 +793,7 @@
 
         // Save the MagneticTarget instance for the newly set up view - we'll add this to the
         // MagnetizedObjects.
-        mMagneticTarget = new MagnetizedObject.MagneticTarget(targetView, dismissRadius);
+        mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius);
 
         mExpandedViewXAnim =
                 new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
@@ -892,7 +909,10 @@
 
             // Update the paint and apply it to the bubble container.
             mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
-            mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
+
+            if (mDesaturateAndDarkenTargetView != null) {
+                mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
+            }
         });
 
         // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts,
@@ -908,6 +928,38 @@
 
             return true;
         });
+
+        animate()
+                .setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
+                .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION);
+    }
+
+    /**
+     * Sets whether or not the stack should become temporarily invisible by moving off the side of
+     * the screen.
+     *
+     * If a flyout comes in while it's invisible, it will animate back in while the flyout is
+     * showing but disappear again when the flyout is gone.
+     */
+    public void setTemporarilyInvisible(boolean invisible) {
+        mTemporarilyInvisible = invisible;
+        animateTemporarilyInvisible();
+    }
+
+    /**
+     * Animates onto or off the screen depending on whether we're temporarily invisible, and whether
+     * a flyout is visible.
+     */
+    private void animateTemporarilyInvisible() {
+        if (mTemporarilyInvisible && mFlyout.getVisibility() != View.VISIBLE) {
+            if (mStackAnimationController.isStackOnLeftSide()) {
+                animate().translationX(-mBubbleSize).start();
+            } else {
+                animate().translationX(mBubbleSize).start();
+            }
+        } else {
+            animate().translationX(0).start();
+        }
     }
 
     private void setUpManageMenu() {
@@ -1123,6 +1175,13 @@
         }
         mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
         mStackAnimationController.updateResources(mOrientation);
+
+        final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+        mDismissTargetCircle.getLayoutParams().width = targetSize;
+        mDismissTargetCircle.getLayoutParams().height = targetSize;
+        mDismissTargetCircle.requestLayout();
+
+        mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
     }
 
     @Override
@@ -1846,6 +1905,10 @@
     private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
         mDesaturateAndDarkenTargetView = targetView;
 
+        if (mDesaturateAndDarkenTargetView == null) {
+            return;
+        }
+
         if (desaturateAndDarken) {
             // Use the animated paint for the bubbles.
             mDesaturateAndDarkenTargetView.setLayerType(
@@ -1867,9 +1930,14 @@
     }
 
     private void resetDesaturationAndDarken() {
+
         mDesaturateAndDarkenAnimator.removeAllListeners();
         mDesaturateAndDarkenAnimator.cancel();
-        mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null);
+
+        if (mDesaturateAndDarkenTargetView != null) {
+            mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null);
+            mDesaturateAndDarkenTargetView = null;
+        }
     }
 
     /** Animates in the dismiss target. */
@@ -1884,6 +1952,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 +1972,9 @@
 
         mShowingDismiss = false;
 
+        ((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition(
+                DISMISS_TRANSITION_DURATION_MS);
+
         mDismissTargetAnimator
                 .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(),
                         mDismissTargetSpring)
@@ -1972,6 +2046,9 @@
             // Stop suppressing the dot now that the flyout has morphed into the dot.
             bubbleView.removeDotSuppressionFlag(
                     BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
+
+            mFlyout.setVisibility(INVISIBLE);
+            animateTemporarilyInvisible();
         };
         mFlyout.setVisibility(INVISIBLE);
 
@@ -1989,6 +2066,7 @@
             final Runnable expandFlyoutAfterDelay = () -> {
                 mAnimateInFlyout = () -> {
                     mFlyout.setVisibility(VISIBLE);
+                    animateTemporarilyInvisible();
                     mFlyoutDragDeltaX =
                             mStackAnimationController.isStackOnLeftSide()
                                     ? -mFlyout.getWidth()
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..5e5ebe9 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -175,7 +175,7 @@
     private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
     private static final String GLOBAL_ACTION_KEY_USERS = "users";
     private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
-    private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
+    static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
     private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
     private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
     static final String GLOBAL_ACTION_KEY_RESTART = "restart";
@@ -213,7 +213,9 @@
     @VisibleForTesting
     protected final ArrayList<Action> mItems = new ArrayList<>();
     @VisibleForTesting
-    final ArrayList<Action> mOverflowItems = new ArrayList<>();
+    protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
+    @VisibleForTesting
+    protected final ArrayList<Action> mPowerItems = new ArrayList<>();
 
     @VisibleForTesting
     protected ActionsDialog mDialog;
@@ -223,6 +225,7 @@
 
     private MyAdapter mAdapter;
     private MyOverflowAdapter mOverflowAdapter;
+    private MyPowerOptionsAdapter mPowerAdapter;
 
     private boolean mKeyguardShowing = false;
     private boolean mDeviceProvisioned = false;
@@ -391,7 +394,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
@@ -576,14 +587,19 @@
 
         mItems.clear();
         mOverflowItems.clear();
-
+        mPowerItems.clear();
         String[] defaultActions = getDefaultActions();
+
+        ShutDownAction shutdownAction = new ShutDownAction();
+        RestartAction restartAction = new RestartAction();
+        ArraySet<String> addedKeys = new ArraySet<String>();
+
         // make sure emergency affordance action is first, if needed
         if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
             addActionItem(new EmergencyAffordanceAction());
+            addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY);
         }
 
-        ArraySet<String> addedKeys = new ArraySet<String>();
         for (int i = 0; i < defaultActions.length; i++) {
             String actionKey = defaultActions[i];
             if (addedKeys.contains(actionKey)) {
@@ -591,7 +607,7 @@
                 continue;
             }
             if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
-                addActionItem(new PowerAction());
+                addActionItem(shutdownAction);
             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                 addActionItem(mAirplaneModeOn);
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
@@ -610,10 +626,7 @@
             } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
                 addActionItem(getSettingsAction());
             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
-                int userId = getCurrentUser().id;
-                if (Settings.Secure.getIntForUser(mContentResolver,
-                        Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) != 0
-                        && shouldDisplayLockdown(userId)) {
+                if (shouldDisplayLockdown(getCurrentUser())) {
                     addActionItem(getLockdownAction());
                 }
             } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
@@ -621,7 +634,7 @@
             } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
                 addActionItem(getAssistAction());
             } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
-                addActionItem(new RestartAction());
+                addActionItem(restartAction);
             } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
                 addActionItem(new ScreenshotAction());
             } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
@@ -630,15 +643,32 @@
                     addActionItem(new LogoutAction());
                 }
             } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
-                if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
-                    addActionItem(new EmergencyDialerAction());
-                }
+                addActionItem(new EmergencyDialerAction());
             } else {
                 Log.e(TAG, "Invalid global action key " + actionKey);
             }
             // Add here so we don't add more than one.
             addedKeys.add(actionKey);
         }
+
+        // replace power and restart with a single power options action, if needed
+        if (mItems.contains(shutdownAction) && mItems.contains(restartAction)
+                && mOverflowItems.size() > 0) {
+            // transfer shutdown and restart to their own list of power actions
+            mItems.remove(shutdownAction);
+            mItems.remove(restartAction);
+            mPowerItems.add(shutdownAction);
+            mPowerItems.add(restartAction);
+
+            // add the PowerOptionsAction after Emergency, if present
+            int powerIndex = addedKeys.contains(GLOBAL_ACTION_KEY_EMERGENCY) ? 1 : 0;
+            mItems.add(powerIndex, new PowerOptionsAction());
+
+            // transfer the first overflow action to the main set of items
+            Action firstOverflowAction = mOverflowItems.get(0);
+            mOverflowItems.remove(0);
+            mItems.add(firstOverflowAction);
+        }
     }
 
     private void onRotate() {
@@ -656,6 +686,7 @@
 
         mAdapter = new MyAdapter();
         mOverflowAdapter = new MyOverflowAdapter();
+        mPowerAdapter = new MyPowerOptionsAdapter();
 
         mDepthController.setShowingHomeControls(true);
         GlobalActionsPanelPlugin.PanelViewController walletViewController =
@@ -668,7 +699,7 @@
                 walletViewController, mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 controlsAvailable(), uiController,
-                mSysUiState, this::onRotate, mKeyguardShowing);
+                mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter);
         boolean walletViewAvailable = walletViewController != null
                 && walletViewController.getPanelContent() != null;
         if (shouldShowLockMessage(walletViewAvailable)) {
@@ -681,7 +712,20 @@
         return dialog;
     }
 
-    private boolean shouldDisplayLockdown(int userId) {
+    @VisibleForTesting
+    protected boolean shouldDisplayLockdown(UserInfo user) {
+        if (user == null) {
+            return false;
+        }
+
+        int userId = user.id;
+
+        // No lockdown option if it's not turned on in Settings
+        if (Settings.Secure.getIntForUser(mContentResolver,
+                Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) == 0) {
+            return false;
+        }
+
         // Lockdown is meaningless without a place to go.
         if (!mKeyguardStateController.isMethodSecure()) {
             return false;
@@ -732,8 +776,32 @@
         mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
     }
 
-    private final class PowerAction extends SinglePressAction implements LongPressAction {
-        private PowerAction() {
+    @VisibleForTesting
+    protected final class PowerOptionsAction extends SinglePressAction {
+        private PowerOptionsAction() {
+            super(R.drawable.ic_lock_power_off, R.string.global_action_power_options);
+        }
+
+        @Override
+        public boolean showDuringKeyguard() {
+            return true;
+        }
+
+        @Override
+        public boolean showBeforeProvisioning() {
+            return true;
+        }
+
+        @Override
+        public void onPress() {
+            if (mDialog != null) {
+                mDialog.showPowerOptionsMenu();
+            }
+        }
+    }
+
+    private final class ShutDownAction extends SinglePressAction implements LongPressAction {
+        private ShutDownAction() {
             super(R.drawable.ic_lock_power_off,
                     R.string.global_action_power_off);
         }
@@ -764,7 +832,8 @@
         }
     }
 
-    private abstract class EmergencyAction extends SinglePressAction {
+    @VisibleForTesting
+    protected abstract class EmergencyAction extends SinglePressAction {
         EmergencyAction(int iconResId, int messageResId) {
             super(iconResId, messageResId);
         }
@@ -1309,7 +1378,10 @@
             Action item = mAdapter.getItem(position);
             if (!(item instanceof SilentModeTriStateAction)) {
                 if (mDialog != null) {
-                    mDialog.dismiss();
+                    // don't dismiss the dialog if we're opening the power options menu
+                    if (!(item instanceof PowerOptionsAction)) {
+                        mDialog.dismiss();
+                    }
                 } else {
                     Log.w(TAG, "Action clicked while mDialog is null.");
                 }
@@ -1326,6 +1398,70 @@
     /**
      * The adapter used for items in the overflow menu.
      */
+    public class MyPowerOptionsAdapter extends BaseAdapter {
+        @Override
+        public int getCount() {
+            return mPowerItems.size();
+        }
+
+        @Override
+        public Action getItem(int position) {
+            return mPowerItems.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            Action action = getItem(position);
+            if (action == null) {
+                Log.w(TAG, "No power options action found at position: " + position);
+                return null;
+            }
+            int viewLayoutResource = com.android.systemui.R.layout.controls_more_item;
+            View view = convertView != null ? convertView
+                    : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
+            TextView textView = (TextView) view;
+            if (action.getMessageResId() != 0) {
+                textView.setText(action.getMessageResId());
+            } else {
+                textView.setText(action.getMessage());
+            }
+            return textView;
+        }
+
+        private boolean onLongClickItem(int position) {
+            final Action action = getItem(position);
+            if (action instanceof LongPressAction) {
+                if (mDialog != null) {
+                    mDialog.dismiss();
+                } else {
+                    Log.w(TAG, "Action long-clicked while mDialog is null.");
+                }
+                return ((LongPressAction) action).onLongPress();
+            }
+            return false;
+        }
+
+        private void onClickItem(int position) {
+            Action item = getItem(position);
+            if (!(item instanceof SilentModeTriStateAction)) {
+                if (mDialog != null) {
+                    mDialog.dismiss();
+                } else {
+                    Log.w(TAG, "Action clicked while mDialog is null.");
+                }
+                item.onPress();
+            }
+        }
+    }
+
+    /**
+     * The adapter used for items in the power options menu, triggered by the PowerOptionsAction.
+     */
     public class MyOverflowAdapter extends BaseAdapter {
         @Override
         public int getCount() {
@@ -1365,7 +1501,6 @@
             final Action action = getItem(position);
             if (action instanceof LongPressAction) {
                 if (mDialog != null) {
-                    mDialog.hidePowerOverflowMenu();
                     mDialog.dismiss();
                 } else {
                     Log.w(TAG, "Action long-clicked while mDialog is null.");
@@ -1379,7 +1514,6 @@
             Action item = getItem(position);
             if (!(item instanceof SilentModeTriStateAction)) {
                 if (mDialog != null) {
-                    mDialog.hidePowerOverflowMenu();
                     mDialog.dismiss();
                 } else {
                     Log.w(TAG, "Action clicked while mDialog is null.");
@@ -1487,7 +1621,6 @@
             }
         }
 
-
         public int getMessageResId() {
             return mMessageResId;
         }
@@ -1838,6 +1971,8 @@
             mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off;
             mAirplaneModeOn.updateState(mAirplaneState);
             mAdapter.notifyDataSetChanged();
+            mOverflowAdapter.notifyDataSetChanged();
+            mPowerAdapter.notifyDataSetChanged();
         }
     };
 
@@ -1920,6 +2055,7 @@
         private final Context mContext;
         private final MyAdapter mAdapter;
         private final MyOverflowAdapter mOverflowAdapter;
+        private final MyPowerOptionsAdapter mPowerOptionsAdapter;
         private final IStatusBarService mStatusBarService;
         private final IBinder mToken = new Binder();
         private MultiListLayout mGlobalActionsLayout;
@@ -1935,6 +2071,7 @@
         private final NotificationShadeDepthController mDepthController;
         private final SysUiState mSysUiState;
         private ListPopupWindow mOverflowPopup;
+        private ListPopupWindow mPowerOptionsPopup;
         private final Runnable mOnRotateCallback;
         private final boolean mControlsAvailable;
 
@@ -1950,11 +2087,13 @@
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
                 boolean controlsAvailable, @Nullable ControlsUiController controlsUiController,
-                SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing) {
+                SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
+                MyPowerOptionsAdapter powerAdapter) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
             mContext = context;
             mAdapter = adapter;
             mOverflowAdapter = overflowAdapter;
+            mPowerOptionsAdapter = powerAdapter;
             mDepthController = depthController;
             mColorExtractor = sysuiColorExtractor;
             mStatusBarService = statusBarService;
@@ -2068,6 +2207,21 @@
             }
         }
 
+        private ListPopupWindow createPowerOptionsPopup() {
+            GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
+                    new ContextThemeWrapper(
+                            mContext,
+                            com.android.systemui.R.style.Control_ListPopupWindow
+                    ), false /* isDropDownMode */);
+            popup.setOnItemClickListener(
+                    (parent, view, position, id) -> mPowerOptionsAdapter.onClickItem(position));
+            popup.setOnItemLongClickListener(
+                    (parent, view, position, id) -> mPowerOptionsAdapter.onLongClickItem(position));
+            popup.setAnchorView(mGlobalActionsLayout);
+            popup.setAdapter(mPowerOptionsAdapter);
+            return popup;
+        }
+
         private ListPopupWindow createPowerOverflowPopup() {
             GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
                     new ContextThemeWrapper(
@@ -2085,16 +2239,16 @@
             return popup;
         }
 
+        public void showPowerOptionsMenu() {
+            mPowerOptionsPopup = createPowerOptionsPopup();
+            mPowerOptionsPopup.show();
+        }
+
         private void showPowerOverflowMenu() {
             mOverflowPopup = createPowerOverflowPopup();
             mOverflowPopup.show();
         }
 
-        private void hidePowerOverflowMenu() {
-            mOverflowPopup.dismiss();
-            mOverflowPopup = null;
-        }
-
         private void initializeLayout() {
             setContentView(com.android.systemui.R.layout.global_actions_grid_v2);
             fixNavBarClipping();
@@ -2270,6 +2424,7 @@
 
                 // close first, as popup windows will not fade during the animation
                 dismissOverflow(false);
+                dismissPowerOptions(false);
                 if (mControlsUiController != null) mControlsUiController.closeDialogs(false);
             });
         }
@@ -2294,6 +2449,7 @@
             resetOrientation();
             dismissWallet();
             dismissOverflow(true);
+            dismissPowerOptions(true);
             if (mControlsUiController != null) mControlsUiController.hide();
             mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
             mDepthController.updateGlobalDialogVisibility(0, null /* view */);
@@ -2318,6 +2474,16 @@
             }
         }
 
+        private void dismissPowerOptions(boolean immediate) {
+            if (mPowerOptionsPopup != null) {
+                if (immediate) {
+                    mPowerOptionsPopup.dismissImmediate();
+                } else {
+                    mPowerOptionsPopup.dismiss();
+                }
+            }
+        }
+
         private void setRotationSuggestionsEnabled(boolean enabled) {
             try {
                 final int userId = Binder.getCallingUserHandle().getIdentifier();
@@ -2361,6 +2527,7 @@
             // ensure dropdown menus are dismissed before re-initializing the dialog
             dismissWallet();
             dismissOverflow(true);
+            dismissPowerOptions(true);
             if (mControlsUiController != null) {
                 mControlsUiController.hide();
             }
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/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index c274ee9..7a9dcde 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -161,6 +161,7 @@
     private float mSavedSnapFraction = -1f;
     private boolean mSendingHoverAccessibilityEvents;
     private boolean mMovementWithinDismiss;
+    private boolean mHideMenuAfterShown = false;
     private PipAccessibilityInteractionConnection mConnection;
 
     // Touch state
@@ -677,6 +678,7 @@
                 break;
             }
             case MotionEvent.ACTION_HOVER_EXIT: {
+                mHideMenuAfterShown = true;
                 // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
                 // on and changing MotionEvents into HoverEvents.
                 // Let's not enable menu show/hide for a11y services.
@@ -767,6 +769,9 @@
                 mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
                         mMovementBounds, mExpandedMovementBounds, callback);
             }
+            if (mHideMenuAfterShown) {
+                mMenuController.hideMenu();
+            }
         } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
             // Try and restore the PiP to the closest edge, using the saved snap fraction
             // if possible
@@ -804,6 +809,7 @@
             }
         }
         mMenuState = menuState;
+        mHideMenuAfterShown = false;
         updateMovementBounds();
         // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
         // as well, or it can't handle a11y focus and pip menu can't perform any action.
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/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 4008918..65d3572 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -355,10 +355,23 @@
     }
 
     public void addTile(ComponentName tile) {
+        addTile(tile, /* end */ false);
+    }
+
+    /**
+     * Adds a custom tile to the set of current tiles.
+     * @param tile the component name of the {@link android.service.quicksettings.TileService}
+     * @param end if true, the tile will be added at the end. If false, at the beginning.
+     */
+    public void addTile(ComponentName tile, boolean end) {
         String spec = CustomTile.toSpec(tile);
         if (!mTileSpecs.contains(spec)) {
             List<String> newSpecs = new ArrayList<>(mTileSpecs);
-            newSpecs.add(0, spec);
+            if (end) {
+                newSpecs.add(spec);
+            } else {
+                newSpecs.add(0, spec);
+            }
             changeTiles(mTileSpecs, newSpecs);
         }
     }
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..2ddd6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -33,9 +33,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.provider.Settings;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.Size;
 import android.widget.Toast;
 
 import com.android.systemui.R;
@@ -225,8 +223,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)
@@ -247,7 +245,8 @@
         startForeground(NOTIFICATION_RECORDING_ID, notification);
     }
 
-    private Notification createSaveNotification(Uri uri) {
+    private Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
+        Uri uri = recording.getUri();
         Intent viewIntent = new Intent(Intent.ACTION_VIEW)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
                 .setDataAndType(uri, "video/mp4");
@@ -290,16 +289,7 @@
                 .addExtras(extras);
 
         // Add thumbnail if available
-        Bitmap thumbnailBitmap = null;
-        try {
-            ContentResolver resolver = getContentResolver();
-            DisplayMetrics metrics = getResources().getDisplayMetrics();
-            Size size = new Size(metrics.widthPixels, metrics.heightPixels / 2);
-            thumbnailBitmap = resolver.loadThumbnail(uri, size, null);
-        } catch (IOException e) {
-            Log.e(TAG, "Error creating thumbnail: " + e.getMessage());
-            e.printStackTrace();
-        }
+        Bitmap thumbnailBitmap = recording.getThumbnail();
         if (thumbnailBitmap != null) {
             Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
                     .bigPicture(thumbnailBitmap)
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..1c7d987 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -22,13 +22,17 @@
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
 
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodecInfo;
 import android.media.MediaMuxer;
 import android.media.MediaRecorder;
+import android.media.ThumbnailUtils;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjection;
@@ -40,6 +44,7 @@
 import android.provider.MediaStore;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Size;
 import android.view.Surface;
 import android.view.WindowManager;
 
@@ -55,9 +60,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 +113,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 +126,16 @@
         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.setVideoEncodingProfileLevel(
+                MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
+                MediaCodecInfo.CodecProfileLevel.AVCLevel42);
         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);
 
@@ -203,7 +214,7 @@
     /**
      * Store recorded video
      */
-    Uri save() throws IOException {
+    protected SavedRecording save() throws IOException {
         String fileName = new SimpleDateFormat("'screen-'yyyyMMdd-HHmmss'.mp4'")
                 .format(new Date());
 
@@ -241,8 +252,38 @@
         OutputStream os = resolver.openOutputStream(itemUri, "w");
         Files.copy(mTempVideoFile.toPath(), os);
         os.close();
-        mTempVideoFile.delete();
         if (mTempAudioFile != null) mTempAudioFile.delete();
-        return itemUri;
+        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+        Size size = new Size(metrics.widthPixels, metrics.heightPixels);
+        SavedRecording recording = new SavedRecording(itemUri, mTempVideoFile, size);
+        mTempVideoFile.delete();
+        return recording;
+    }
+
+    /**
+    * Object representing the recording
+    */
+    public class SavedRecording {
+
+        private Uri mUri;
+        private Bitmap mThumbnailBitmap;
+
+        protected SavedRecording(Uri uri, File file, Size thumbnailSize) {
+            mUri = uri;
+            try {
+                mThumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
+                        file, thumbnailSize, null);
+            } catch (IOException e) {
+                Log.e(TAG, "Error creating thumbnail", e);
+            }
+        }
+
+        public Uri getUri() {
+            return mUri;
+        }
+
+        public @Nullable Bitmap getThumbnail() {
+            return mThumbnailBitmap;
+        }
     }
 }
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..a624479 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);
     }
@@ -640,6 +643,18 @@
      */
     private void showUiOnActionsReady(SavedImageData imageData) {
         logSuccessOnActionsReady(imageData);
+
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
+                SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+
+        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+        mScreenshotHandler.sendMessageDelayed(
+                mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+                timeoutMs);
+
         if (imageData.uri != null) {
             mScreenshotHandler.post(() -> {
                 if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
@@ -653,17 +668,6 @@
                 } else {
                     createScreenshotActionsShadeAnimation(imageData).start();
                 }
-
-                AccessibilityManager accessibilityManager = (AccessibilityManager)
-                        mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-                long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
-                        SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
-                        AccessibilityManager.FLAG_CONTENT_CONTROLS);
-
-                mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
-                mScreenshotHandler.sendMessageDelayed(
-                        mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
-                        timeoutMs);
             });
         }
     }
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/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index a5bab21..221174f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -123,6 +123,7 @@
         if (isCancelled()) {
             return null;
         }
+        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
 
         ContentResolver resolver = mContext.getContentResolver();
         Bitmap image = mParams.image;
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/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index b163818..93db9cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -341,7 +341,6 @@
     }
 
     private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
-            or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
             or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
             or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
 }
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/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
index 88c3258..c88f0bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
@@ -184,7 +184,6 @@
     }
 
     private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
-            or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
             or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
             or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
 }
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/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index ba7675f..0bdac39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -287,21 +287,17 @@
                     // Is there a section discontinuity? This usually occurs due to HUNs
                     if (prev?.entry?.bucket?.let { it > child.entry.bucket } == true) {
                         // Remove existing headers, and move the Incoming header if necessary
-                        if (alertingHeaderTarget != -1) {
-                            if (showHeaders && incomingHeaderTarget != -1) {
-                                incomingHeaderTarget = alertingHeaderTarget
-                            }
-                            alertingHeaderTarget = -1
+                        incomingHeaderTarget = when {
+                            !showHeaders -> -1
+                            incomingHeaderTarget != -1 -> incomingHeaderTarget
+                            peopleHeaderTarget != -1 -> peopleHeaderTarget
+                            alertingHeaderTarget != -1 -> alertingHeaderTarget
+                            gentleHeaderTarget != -1 -> gentleHeaderTarget
+                            else -> 0
                         }
-                        if (peopleHeaderTarget != -1) {
-                            if (showHeaders && incomingHeaderTarget != -1) {
-                                incomingHeaderTarget = peopleHeaderTarget
-                            }
-                            peopleHeaderTarget = -1
-                        }
-                        if (showHeaders && incomingHeaderTarget == -1) {
-                            incomingHeaderTarget = 0
-                        }
+                        peopleHeaderTarget = -1
+                        alertingHeaderTarget = -1
+                        gentleHeaderTarget = -1
                         // Walk backwards changing all previous notifications to the Incoming
                         // section
                         for (j in i - 1 downTo lastIncomingIndex + 1) {
@@ -323,6 +319,9 @@
                                 peopleHeaderTarget = i
                                 // Offset the target if there are other headers before this that
                                 // will be moved.
+                                if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) {
+                                    peopleHeaderTarget--
+                                }
                                 if (currentPeopleHeaderIdx != -1) {
                                     peopleHeaderTarget--
                                 }
@@ -340,6 +339,13 @@
                                 alertingHeaderTarget = i
                                 // Offset the target if there are other headers before this that
                                 // will be moved.
+                                if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) {
+                                    alertingHeaderTarget--
+                                }
+                                if (currentPeopleHeaderIdx != -1 && peopleHeaderTarget == -1) {
+                                    // People header will be removed
+                                    alertingHeaderTarget--
+                                }
                                 if (currentAlertingHeaderIdx != -1) {
                                     alertingHeaderTarget--
                                 }
@@ -354,6 +360,17 @@
                                 gentleHeaderTarget = i
                                 // Offset the target if there are other headers before this that
                                 // will be moved.
+                                if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) {
+                                    gentleHeaderTarget--
+                                }
+                                if (currentPeopleHeaderIdx != -1 && peopleHeaderTarget == -1) {
+                                    // People header will be removed
+                                    gentleHeaderTarget--
+                                }
+                                if (currentAlertingHeaderIdx != -1 && alertingHeaderTarget == -1) {
+                                    // Alerting header will be removed
+                                    gentleHeaderTarget--
+                                }
                                 if (currentGentleHeaderIdx != -1) {
                                     gentleHeaderTarget--
                                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 7951541..fc8c8db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -274,7 +274,7 @@
             }
             if (value != 0) {
                 if (mSpec.startsWith(CustomTile.PREFIX)) {
-                    mHost.addTile(CustomTile.getComponentFromSpec(mSpec));
+                    mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true);
                 } else {
                     mHost.addTile(mSpec);
                 }
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/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index e0e52001..1bc42d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2253,6 +2253,8 @@
                 updateHideIconsForBouncer(false /* animate */);
             }
         }
+
+        updateBubblesVisibility();
     }
 
     @Override
@@ -2268,6 +2270,8 @@
         }
         mLightBarController.onStatusBarAppearanceChanged(appearanceRegions, barModeChanged,
                 mStatusBarMode, navbarColorManagedByIme);
+
+        updateBubblesVisibility();
     }
 
     @Override
@@ -2311,6 +2315,7 @@
         final int barMode = barMode(mTransientShown, mAppearance);
         if (updateBarMode(barMode)) {
             mLightBarController.onStatusBarModeChanged(barMode);
+            updateBubblesVisibility();
         }
     }
 
@@ -2395,6 +2400,14 @@
         mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
     }
 
+    /** Temporarily hides Bubbles if the status bar is hidden. */
+    private void updateBubblesVisibility() {
+        mBubbleController.onStatusBarVisibilityChanged(
+                mStatusBarMode != MODE_LIGHTS_OUT
+                        && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
+                        && !mStatusBarWindowHidden);
+    }
+
     void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
             BarTransitions transitions) {
         final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
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/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index ce032e2..3455ff4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -212,7 +212,6 @@
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
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/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 3254633..329af2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -244,7 +245,7 @@
     }
 
     @Test
-    public void testCreateActionItems_maxThree() {
+    public void testCreateActionItems_maxThree_noOverflow() {
         mGlobalActionsDialog = spy(mGlobalActionsDialog);
         // allow 3 items to be shown
         doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
@@ -254,13 +255,129 @@
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
+        mGlobalActionsDialog.createActionItems();
+
+        assertEquals(3, mGlobalActionsDialog.mItems.size());
+        assertEquals(0, mGlobalActionsDialog.mOverflowItems.size());
+        assertEquals(0, mGlobalActionsDialog.mPowerItems.size());
+    }
+
+    @Test
+    public void testCreateActionItems_maxThree_condensePower() {
+        mGlobalActionsDialog = spy(mGlobalActionsDialog);
+        // allow 3 items to be shown
+        doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
+        // ensure items are not blocked by keyguard or device provisioning
+        doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
+        // make sure lockdown action will be shown
+        doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+        };
+        doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
+        mGlobalActionsDialog.createActionItems();
+
+        assertEquals(3, mGlobalActionsDialog.mItems.size());
+        assertEquals(0, mGlobalActionsDialog.mOverflowItems.size());
+        assertEquals(2, mGlobalActionsDialog.mPowerItems.size());
+
+        // PowerOptionsAction should appear immediately after the Emergency action
+
+        GlobalActionsDialog.Action firstItem = mGlobalActionsDialog.mItems.get(0);
+        GlobalActionsDialog.Action secondItem = mGlobalActionsDialog.mItems.get(1);
+
+        assertTrue(firstItem instanceof GlobalActionsDialog.EmergencyAction);
+        assertTrue(secondItem instanceof GlobalActionsDialog.PowerOptionsAction);
+    }
+
+    @Test
+    public void testCreateActionItems_maxThree_condensePower_noEmergency() {
+        mGlobalActionsDialog = spy(mGlobalActionsDialog);
+        // allow 3 items to be shown
+        doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
+        // make sure lockdown action will be shown
+        doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
+        // ensure items are not blocked by keyguard or device provisioning
+        doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+        };
+        doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
+        mGlobalActionsDialog.createActionItems();
+
+        assertEquals(3, mGlobalActionsDialog.mItems.size());
+        assertEquals(0, mGlobalActionsDialog.mOverflowItems.size());
+        assertEquals(2, mGlobalActionsDialog.mPowerItems.size());
+
+        // When Emergency isn't used, PowerOptionsAction should be first
+
+        GlobalActionsDialog.Action firstItem = mGlobalActionsDialog.mItems.get(0);
+        GlobalActionsDialog.Action secondItem = mGlobalActionsDialog.mItems.get(1);
+
+        assertTrue(firstItem instanceof GlobalActionsDialog.PowerOptionsAction);
+        assertTrue(secondItem instanceof GlobalActionsDialog.ScreenshotAction);
+    }
+
+    @Test
+    public void testCreateActionItems_maxFour_condensePower() {
+        mGlobalActionsDialog = spy(mGlobalActionsDialog);
+        // allow 3 items to be shown
+        doReturn(4).when(mGlobalActionsDialog).getMaxShownPowerItems();
+        // make sure lockdown action will be shown
+        doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
+        // ensure items are not blocked by keyguard or device provisioning
+        doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT
+        };
+        doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
+        mGlobalActionsDialog.createActionItems();
+
+        assertEquals(4, mGlobalActionsDialog.mItems.size());
+        assertEquals(0, mGlobalActionsDialog.mOverflowItems.size());
+        assertEquals(2, mGlobalActionsDialog.mPowerItems.size());
+
+        // with four items, make sure power still shows up immediately after Emergency
+        GlobalActionsDialog.Action firstItem = mGlobalActionsDialog.mItems.get(0);
+        GlobalActionsDialog.Action secondItem = mGlobalActionsDialog.mItems.get(1);
+
+        assertTrue(firstItem instanceof GlobalActionsDialog.EmergencyAction);
+        assertTrue(secondItem instanceof GlobalActionsDialog.PowerOptionsAction);
+    }
+
+    @Test
+    public void testCreateActionItems_maxThree_doNotCondensePower() {
+        mGlobalActionsDialog = spy(mGlobalActionsDialog);
+        // allow 3 items to be shown
+        doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
+        // make sure lockdown action will be shown
+        doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
+        // ensure items are not blocked by keyguard or device provisioning
+        doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
         };
         doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
         mGlobalActionsDialog.createActionItems();
 
         assertEquals(3, mGlobalActionsDialog.mItems.size());
         assertEquals(1, mGlobalActionsDialog.mOverflowItems.size());
+        assertEquals(0, mGlobalActionsDialog.mPowerItems.size());
     }
 
     @Test
@@ -270,11 +387,13 @@
         doReturn(Integer.MAX_VALUE).when(mGlobalActionsDialog).getMaxShownPowerItems();
         // ensure items are not blocked by keyguard or device provisioning
         doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
+        // make sure lockdown action will be shown
+        doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
         String[] actions = {
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
-                GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
         };
         doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
         mGlobalActionsDialog.createActionItems();
@@ -288,10 +407,12 @@
         mGlobalActionsDialog = spy(mGlobalActionsDialog);
         // allow only 3 items to be shown
         doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
+        // make sure lockdown action will NOT be shown
+        doReturn(false).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
         String[] actions = {
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
-                // screenshot blocked because device not provisioned
-                GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT,
+                // lockdown action not allowed
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
                 GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
         };
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/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 1147739..5d4ef55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -219,13 +219,43 @@
     public void testNoRepeatedSpecs_customTile() {
         mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC);
 
-        mQSTileHost.addTile(CUSTOM_TILE);
+        mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
 
         assertEquals(1, mQSTileHost.mTileSpecs.size());
         assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
     }
 
     @Test
+    public void testAddedAtBeginningOnDefault_customTile() {
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+
+        mQSTileHost.addTile(CUSTOM_TILE);
+
+        assertEquals(2, mQSTileHost.mTileSpecs.size());
+        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+    }
+
+    @Test
+    public void testAddedAtBeginning_customTile() {
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+
+        mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
+
+        assertEquals(2, mQSTileHost.mTileSpecs.size());
+        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+    }
+
+    @Test
+    public void testAddedAtEnd_customTile() {
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+
+        mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
+
+        assertEquals(2, mQSTileHost.mTileSpecs.size());
+        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(1));
+    }
+
+    @Test
     public void testLoadTileSpec_repeated() {
         List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2");
 
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/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 3dc941a..c55391a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -403,11 +403,11 @@
         enablePeopleFiltering();
 
         setupMockStack(
-                PERSON.headsUp(),
-                INCOMING_HEADER,
-                ALERTING.headsUp(),
-                PEOPLE_HEADER,
-                PERSON
+                PERSON.headsUp(),   // personHeaderTarget = 0
+                INCOMING_HEADER,    // currentIncomingHeaderIdx = 1
+                ALERTING.headsUp(), // alertingHeaderTarget = 1
+                PEOPLE_HEADER,      // currentPeopleHeaderIdx = 3
+                PERSON              //
         );
         mSectionsManager.updateSectionBoundaries();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 1a6921a..05cdd80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -172,11 +172,12 @@
     }
 
     @Test
-    public void testSettingTileAddedComponent_onChanged() {
+    public void testSettingTileAddedComponentAtEnd_onChanged() {
         changeValue(TEST_SETTING_COMPONENT, 1);
         waitForIdleSync();
         verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
-        verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT));
+        verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
+            , /* end */ true);
     }
 
     @Test
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b5aec8e..8eb401a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4657,9 +4657,8 @@
         private void killAppForOpChange(int code, int uid, String packageName) {
             final IActivityManager am = ActivityManager.getService();
             try {
-                am.killApplication(packageName,
-                        UserHandle.getAppId(uid),
-                        UserHandle.USER_ALL, AppOpsManager.opToName(code) + " changed.");
+                am.killUid(UserHandle.getAppId(uid), UserHandle.USER_ALL,
+                        AppOpsManager.opToName(code) + " changed.");
             } catch (RemoteException e) {
             }
         }
@@ -4681,7 +4680,12 @@
                                 // results in a bad UX, especially since the gid only gives access
                                 // to unreliable volumes, USB OTGs that are rarely mounted. The app
                                 // will get the external_storage gid on next organic restart.
-                                killAppForOpChange(code, uid, packageName);
+                                if (packageName != null) {
+                                    killAppForOpChange(code, uid, packageName);
+                                } else {
+                                    // TODO(b/158283222) this can happen, figure out if we need
+                                    // to kill in this case as well.
+                                }
                             }
                             return;
                         case OP_LEGACY_STORAGE:
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/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index 13f0f4ae..5d913d1 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -64,12 +64,6 @@
     }
 
     private void handleSendGiveAudioStatusFailure() {
-        // Inform to all application that the audio status (volume, mute) of
-        // the audio amplifier is unknown.
-        tv().setAudioStatus(false, Constants.UNKNOWN_VOLUME);
-
-        sendUserControlPressedAndReleased(mAvrAddress,
-                HdmiCecKeycode.getMuteKey(!tv().isSystemAudioActivated()));
 
         // Still return SUCCESS to callback.
         finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
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/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/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 1eb7d95..439f231 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -252,29 +252,6 @@
   Printer* printer_;
 };
 
-std::string OverlayablePoliciesToString(PolicyFlags policies) {
-  std::string str;
-
-  uint32_t remaining = policies;
-  for (auto const& policy : kPolicyStringToFlag) {
-    if ((policies & policy.second) != policy.second) {
-      continue;
-    }
-    if (!str.empty()) {
-      str.append("|");
-    }
-    str.append(policy.first.data());
-    remaining &= ~policy.second;
-  }
-  if (remaining != 0) {
-    if (!str.empty()) {
-      str.append("|");
-    }
-    str.append(StringPrintf("0x%08x", remaining));
-  }
-  return !str.empty() ? str : "none";
-}
-
 }  // namespace
 
 void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
@@ -575,7 +552,7 @@
               overlayable_item.overlayable->name.c_str(),
               overlayable_item.overlayable->actor.c_str());
           const auto policy_subsection = StringPrintf(R"(policies="%s")",
-              OverlayablePoliciesToString(overlayable_item.policies).c_str());
+              android::idmap2::policy::PoliciesToDebugString(overlayable_item.policies).c_str());
           const auto value =
             StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str());
           items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value});
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