Merge RP1A.200513.002

Change-Id: I4af65528de8e3ff3dce71e5de8249a9e658b0a1c
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 2aa2275..e888651 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -56,6 +56,7 @@
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.LimitExceededException;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
@@ -1002,7 +1003,7 @@
                     }
                     if (isDebuggable) {
                         // Only throw the exception for debuggable apps.
-                        throw new IllegalStateException(
+                        throw new LimitExceededException(
                                 "schedule()/enqueue() called more than "
                                         + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
                                         + " times in the past "
diff --git a/api/current.txt b/api/current.txt
index 467aa32..41a74cf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -58326,9 +58326,9 @@
     method public abstract void setAllowFileAccess(boolean);
     method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
     method @Deprecated public abstract void setAllowUniversalAccessFromFileURLs(boolean);
-    method public abstract void setAppCacheEnabled(boolean);
+    method @Deprecated public abstract void setAppCacheEnabled(boolean);
     method @Deprecated public abstract void setAppCacheMaxSize(long);
-    method public abstract void setAppCachePath(String);
+    method @Deprecated public abstract void setAppCachePath(String);
     method public abstract void setBlockNetworkImage(boolean);
     method public abstract void setBlockNetworkLoads(boolean);
     method public abstract void setBuiltInZoomControls(boolean);
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index f314358..6428cf4 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1317,7 +1317,7 @@
     if (mSystemWd < 0) {
         close(mInotifyFd);
         mInotifyFd = -1;
-        SLOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH);
+        SLOGE("Could not add watch for %s: %s", SYSTEM_DATA_DIR_PATH, strerror(errno));
         return NO_INIT;
     }
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 71dd4dd..1d9f20e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -439,6 +439,7 @@
             app_permission_groups_fragment_auto_revoke_action =
             273 [(module) = "permissioncontroller"];
         EvsUsageStatsReported evs_usage_stats_reported = 274 [(module) = "evs"];
+        AudioPowerUsageDataReported audio_power_usage_data_reported = 275;
         SdkExtensionStatus sdk_extension_status = 354;
 
         // StatsdStats tracks platform atoms with ids upto 500.
@@ -9689,3 +9690,59 @@
     // The duration of the service
     optional int64 duration_millis = 10;
 }
+
+/**
+ * Logs audio power usage stats.
+ *
+ * Pushed from:
+ *  frameworks/av/services/mediametrics/AudioPowerUsage.cpp
+ */
+message AudioPowerUsageDataReported {
+    /**
+     * Device used for input/output
+     *
+     * All audio devices please refer to below file:
+     * system/media/audio/include/system/audio-base.h
+     *
+     * Define our own enum values because we don't report all audio devices.
+     * Currently, we only report built-in audio devices such as handset, speaker,
+     * built-in mics, common audio devices such as wired headset, usb headset
+     * and bluetooth devices.
+     */
+    enum AudioDevice {
+        OUTPUT_EARPIECE         = 0x1; // handset
+        OUTPUT_SPEAKER          = 0x2; // dual speaker
+        OUTPUT_WIRED_HEADSET    = 0x4; // 3.5mm headset
+        OUTPUT_USB_HEADSET      = 0x8; // usb headset
+        OUTPUT_BLUETOOTH_SCO    = 0x10; // bluetooth sco
+        OUTPUT_BLUETOOTH_A2DP   = 0x20; // a2dp
+        OUTPUT_SPEAKER_SAFE     = 0x40; // bottom speaker
+
+        INPUT_DEVICE_BIT        = 0x40000000; // non-negative positive int32.
+        INPUT_BUILTIN_MIC       = 0x40000001; // buildin mic
+        INPUT_BUILTIN_BACK_MIC  = 0x40000002; // buildin back mic
+        INPUT_WIRED_HEADSET_MIC = 0x40000004; // 3.5mm headset mic
+        INPUT_USB_HEADSET_MIC   = 0x40000008; // usb headset mic
+        INPUT_BLUETOOTH_SCO     = 0x40000010; // bluetooth sco mic
+    }
+    optional AudioDevice audio_device = 1;
+
+    // Duration of the audio in seconds
+    optional int32 duration_secs = 2;
+
+    // Average volume (0 ... 1.0)
+    optional float average_volume = 3;
+
+    enum AudioType {
+        UNKNOWN_TYPE = 0;
+        VOICE_CALL_TYPE = 1; // voice call
+        VOIP_CALL_TYPE = 2; // voip call, including uplink and downlink
+        MEDIA_TYPE = 3; // music and system sound
+        RINGTONE_NOTIFICATION_TYPE = 4; // ringtone and notification
+        ALARM_TYPE = 5; // alarm type
+        // record type
+        CAMCORDER_TYPE = 6; // camcorder
+        RECORD_TYPE = 7;  // other recording
+    }
+    optional AudioType type = 4;
+}
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 8ec0173..f56fa62 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -66,15 +66,6 @@
 #define ATTRIBUTION_CHAIN_TYPE 0x09
 #define ERROR_TYPE 0x0F
 
-LogEvent::LogEvent(const LogEvent& event) {
-    mTagId = event.mTagId;
-    mLogUid = event.mLogUid;
-    mLogPid = event.mLogPid;
-    mElapsedTimestampNs = event.mElapsedTimestampNs;
-    mLogdTimestampNs = event.mLogdTimestampNs;
-    mValues = event.mValues;
-}
-
 LogEvent::LogEvent(int32_t uid, int32_t pid)
     : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
 }
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 53fb5d9..a5f2460 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -216,7 +216,7 @@
     /**
      * Only use this if copy is absolutely needed.
      */
-    LogEvent(const LogEvent&);
+    LogEvent(const LogEvent&) = default;
 
     void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
     void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bfae632..af5fafb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,8 @@
 package android.app;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.inMultiWindowMode;
 import static android.os.Process.myUid;
 
 import static java.lang.Character.MIN_VALUE;
@@ -947,9 +949,8 @@
     /** @hide */
     boolean mEnterAnimationComplete;
 
-    /** Track last dispatched multi-window and PiP mode to client, internal debug purpose **/
-    private Boolean mLastDispatchedIsInMultiWindowMode;
-    private Boolean mLastDispatchedIsInPictureInPictureMode;
+    private boolean mIsInMultiWindowMode;
+    private boolean mIsInPictureInPictureMode;
 
     private final WindowControllerCallback mWindowControllerCallback =
             new WindowControllerCallback() {
@@ -2748,7 +2749,7 @@
      * @return True if the activity is in multi-window mode.
      */
     public boolean isInMultiWindowMode() {
-        return mLastDispatchedIsInMultiWindowMode == Boolean.TRUE;
+        return mIsInMultiWindowMode;
     }
 
     /**
@@ -2791,7 +2792,7 @@
      * @return True if the activity is in picture-in-picture mode.
      */
     public boolean isInPictureInPictureMode() {
-        return mLastDispatchedIsInPictureInPictureMode == Boolean.TRUE;
+        return mIsInPictureInPictureMode;
     }
 
     /**
@@ -7142,14 +7143,19 @@
                 writer.print(mResumed); writer.print(" mStopped=");
                 writer.print(mStopped); writer.print(" mFinished=");
                 writer.println(mFinished);
-        writer.print(innerPrefix); writer.print("mLastDispatchedIsInMultiWindowMode=");
-                writer.print(mLastDispatchedIsInMultiWindowMode);
-                writer.print(" mLastDispatchedIsInPictureInPictureMode=");
-                writer.println(mLastDispatchedIsInPictureInPictureMode);
+        writer.print(innerPrefix); writer.print("mIsInMultiWindowMode=");
+                writer.print(mIsInMultiWindowMode);
+                writer.print(" mIsInPictureInPictureMode=");
+                writer.println(mIsInPictureInPictureMode);
         writer.print(innerPrefix); writer.print("mChangingConfigurations=");
                 writer.println(mChangingConfigurations);
         writer.print(innerPrefix); writer.print("mCurrentConfig=");
                 writer.println(mCurrentConfig);
+        if (getResources().hasOverrideDisplayAdjustments()) {
+            writer.print(innerPrefix);
+            writer.print("FixedRotationAdjustments=");
+            writer.println(getResources().getDisplayAdjustments().getFixedRotationAdjustments());
+        }
 
         mFragments.dumpLoaders(innerPrefix, fd, writer, args);
         mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args);
@@ -7977,6 +7983,11 @@
     final void performCreate(Bundle icicle, PersistableBundle persistentState) {
         dispatchActivityPreCreated(icicle);
         mCanEnterPictureInPicture = true;
+        // initialize mIsInMultiWindowMode and mIsInPictureInPictureMode before onCreate
+        final int windowingMode = getResources().getConfiguration().windowConfiguration
+                .getWindowingMode();
+        mIsInMultiWindowMode = inMultiWindowMode(windowingMode);
+        mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED;
         restoreHasCurrentPermissionRequest(icicle);
         if (persistentState != null) {
             onCreate(icicle, persistentState);
@@ -8245,7 +8256,7 @@
         if (mWindow != null) {
             mWindow.onMultiWindowModeChanged();
         }
-        mLastDispatchedIsInMultiWindowMode = isInMultiWindowMode;
+        mIsInMultiWindowMode = isInMultiWindowMode;
         onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
     }
 
@@ -8258,7 +8269,7 @@
         if (mWindow != null) {
             mWindow.onPictureInPictureModeChanged(isInPictureInPictureMode);
         }
-        mLastDispatchedIsInPictureInPictureMode = isInPictureInPictureMode;
+        mIsInPictureInPictureMode = isInPictureInPictureMode;
         onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41e3891..a83ecf6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -157,6 +157,8 @@
 import android.view.Choreographer;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.ViewDebug;
@@ -216,6 +218,7 @@
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 final class RemoteServiceException extends AndroidRuntimeException {
     public RemoteServiceException(String msg) {
@@ -406,6 +409,9 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final ResourcesManager mResourcesManager;
 
+    /** The active adjustments that override the {@link DisplayAdjustments} in resources. */
+    private ArrayList<Pair<IBinder, Consumer<DisplayAdjustments>>> mActiveRotationAdjustments;
+
     // Registry of remote cancellation transports pending a reply with reply handles.
     @GuardedBy("this")
     private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
@@ -542,6 +548,12 @@
         @UnsupportedAppUsage
         boolean mPreserveWindow;
 
+        /**
+         * If non-null, the activity is launching with a specified rotation, the adjustments should
+         * be consumed before activity creation.
+         */
+        FixedRotationAdjustments mPendingFixedRotationAdjustments;
+
         @LifecycleState
         private int mLifecycleState = PRE_ON_CREATE;
 
@@ -558,7 +570,7 @@
                 PersistableBundle persistentState, List<ResultInfo> pendingResults,
                 List<ReferrerIntent> pendingNewIntents, boolean isForward,
                 ProfilerInfo profilerInfo, ClientTransactionHandler client,
-                IBinder assistToken) {
+                IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments) {
             this.token = token;
             this.assistToken = assistToken;
             this.ident = ident;
@@ -576,6 +588,7 @@
             this.overrideConfig = overrideConfig;
             this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
                     compatInfo);
+            mPendingFixedRotationAdjustments = fixedRotationAdjustments;
             init();
         }
 
@@ -3234,6 +3247,44 @@
         sendMessage(H.CLEAN_UP_CONTEXT, cci);
     }
 
+    @Override
+    public void handleFixedRotationAdjustments(@NonNull IBinder token,
+            @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
+        final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
+                ? displayAdjustments -> displayAdjustments.setFixedRotationAdjustments(
+                        fixedRotationAdjustments)
+                : 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;
+        }
+
+        // Apply the last override to application resources for compatibility. Because the Resources
+        // of Display can be from application, e.g.
+        //    applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
+        // and the deprecated usage:
+        //    applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
+        final Consumer<DisplayAdjustments> appOverride;
+        if (mActiveRotationAdjustments == null) {
+            mActiveRotationAdjustments = new ArrayList<>(2);
+        }
+        if (override != null) {
+            mActiveRotationAdjustments.add(Pair.create(token, override));
+            appOverride = override;
+        } else {
+            mActiveRotationAdjustments.removeIf(adjustmentsPair -> adjustmentsPair.first == token);
+            appOverride = mActiveRotationAdjustments.isEmpty()
+                    ? null
+                    : mActiveRotationAdjustments.get(mActiveRotationAdjustments.size() - 1).second;
+        }
+        mInitialApplication.getResources().overrideDisplayAdjustments(appOverride);
+    }
+
     /**  Core implementation of activity launch. */
     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         ActivityInfo aInfo = r.activityInfo;
@@ -3447,6 +3498,13 @@
         ContextImpl appContext = ContextImpl.createActivityContext(
                 this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
 
+        // 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);
+            r.mPendingFixedRotationAdjustments = null;
+        }
+
         final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
         // For debugging purposes, if the activity's package name contains the value of
         // the "debug.use-second-display" system property as a substring, then show
@@ -7496,7 +7554,15 @@
             try {
                 super.rename(oldPath, newPath);
             } catch (ErrnoException e) {
-                if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/")) {
+                // On emulated volumes, we have bind mounts for /Android/data and
+                // /Android/obb, which prevents move from working across those directories
+                // and other directories on the filesystem. To work around that, try to
+                // recover by doing a copy instead.
+                // Note that we only do this for "/storage/emulated", because public volumes
+                // don't have these bind mounts, neither do private volumes that are not
+                // the primary storage.
+                if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/emulated")
+                        && newPath.startsWith("/storage/emulated")) {
                     Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath);
                     try {
                         Files.move(new File(oldPath).toPath(), new File(newPath).toPath(),
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d321288..b058dcd 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3688,7 +3688,7 @@
         /**
          * @hide
          */
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
+        @UnsupportedAppUsage(/*maxTargetSdk = Build.VERSION_CODES.R,*/ publicAlternatives = "{@code "
                 + "#getOpStr()}")
         public int getOp() {
             return mOp;
@@ -3707,7 +3707,7 @@
          * @deprecated Use {@link #getLastAccessTime(int)} instead
          */
         @Deprecated
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
+        @UnsupportedAppUsage(/*maxTargetSdk = Build.VERSION_CODES.R,*/ publicAlternatives = "{@code "
                 + "#getLastAccessTime(int)}")
         public long getTime() {
             return getLastAccessTime(OP_FLAGS_ALL);
@@ -3822,7 +3822,7 @@
          * @deprecated Use {@link #getLastRejectTime(int)} instead
          */
         @Deprecated
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
+        @UnsupportedAppUsage(/*maxTargetSdk = Build.VERSION_CODES.R,*/ publicAlternatives = "{@code "
                 + "#getLastRejectTime(int)}")
         public long getRejectTime() {
             return getLastRejectTime(OP_FLAGS_ALL);
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 83465b0..2df756e 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -25,6 +25,7 @@
 import android.content.res.Configuration;
 import android.os.IBinder;
 import android.util.MergedConfiguration;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.ReferrerIntent;
@@ -167,6 +168,10 @@
     /** Deliver app configuration change notification. */
     public abstract void handleConfigurationChanged(Configuration config);
 
+    /** Apply addition adjustments to override display information. */
+    public abstract void handleFixedRotationAdjustments(IBinder token,
+            FixedRotationAdjustments fixedRotationAdjustments);
+
     /**
      * Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
      * provided token.
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 72f5813..1b6ca11 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -59,6 +59,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.WeakHashMap;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /** @hide */
@@ -1296,6 +1297,35 @@
         }
     }
 
+    /**
+     * Overrides the display adjustments of all resources which are associated with the given token.
+     *
+     * @param token The token that owns the resources.
+     * @param override The operation to override the existing display adjustments. If it is null,
+     *                 the override adjustments will be cleared.
+     * @return {@code true} if the override takes effect.
+     */
+    public boolean overrideTokenDisplayAdjustments(IBinder token,
+            @Nullable Consumer<DisplayAdjustments> override) {
+        boolean handled = false;
+        synchronized (this) {
+            final ActivityResources tokenResources = mActivityResourceReferences.get(token);
+            if (tokenResources == null) {
+                return false;
+            }
+            final ArrayList<WeakReference<Resources>> resourcesRefs =
+                    tokenResources.activityResources;
+            for (int i = resourcesRefs.size() - 1; i >= 0; i--) {
+                final Resources res = resourcesRefs.get(i).get();
+                if (res != null) {
+                    res.overrideDisplayAdjustments(override);
+                    handled = true;
+                }
+            }
+        }
+        return handled;
+    }
+
     private class UpdateHandler implements Resources.UpdateCallbacks {
 
         /**
diff --git a/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java b/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
new file mode 100644
index 0000000..6183d5f
--- /dev/null
+++ b/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
@@ -0,0 +1,112 @@
+/*
+ * 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.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
+
+import java.util.Objects;
+
+/**
+ * The request to update display adjustments for a rotated activity or window token.
+ * @hide
+ */
+public class FixedRotationAdjustmentsItem extends ClientTransactionItem {
+
+    /** The token who may have {@link android.content.res.Resources}. */
+    private IBinder mToken;
+
+    /**
+     * The adjustments for the display adjustments of resources. If it is null, the existing
+     * rotation adjustments will be dropped to restore natural state.
+     */
+    private FixedRotationAdjustments mFixedRotationAdjustments;
+
+    private FixedRotationAdjustmentsItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static FixedRotationAdjustmentsItem obtain(IBinder token,
+            FixedRotationAdjustments fixedRotationAdjustments) {
+        FixedRotationAdjustmentsItem instance =
+                ObjectPool.obtain(FixedRotationAdjustmentsItem.class);
+        if (instance == null) {
+            instance = new FixedRotationAdjustmentsItem();
+        }
+        instance.mToken = token;
+        instance.mFixedRotationAdjustments = fixedRotationAdjustments;
+
+        return instance;
+    }
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        client.handleFixedRotationAdjustments(mToken, mFixedRotationAdjustments);
+    }
+
+    @Override
+    public void recycle() {
+        mToken = null;
+        mFixedRotationAdjustments = null;
+        ObjectPool.recycle(this);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+        dest.writeTypedObject(mFixedRotationAdjustments, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final FixedRotationAdjustmentsItem other = (FixedRotationAdjustmentsItem) o;
+        return Objects.equals(mToken, other.mToken)
+                && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mToken);
+        result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
+        return result;
+    }
+
+    private FixedRotationAdjustmentsItem(Parcel in) {
+        mToken = in.readStrongBinder();
+        mFixedRotationAdjustments = in.readTypedObject(FixedRotationAdjustments.CREATOR);
+    }
+
+    public static final Creator<FixedRotationAdjustmentsItem> CREATOR =
+            new Creator<FixedRotationAdjustmentsItem>() {
+        public FixedRotationAdjustmentsItem createFromParcel(Parcel in) {
+            return new FixedRotationAdjustmentsItem(in);
+        }
+
+        public FixedRotationAdjustmentsItem[] newArray(int size) {
+            return new FixedRotationAdjustmentsItem[size];
+        }
+    };
+}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 9ab6e7f..2e7b626 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -33,6 +33,7 @@
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.Trace;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
 
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.ReferrerIntent;
@@ -64,6 +65,7 @@
     private boolean mIsForward;
     private ProfilerInfo mProfilerInfo;
     private IBinder mAssistToken;
+    private FixedRotationAdjustments mFixedRotationAdjustments;
 
     @Override
     public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -79,7 +81,7 @@
         ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                 mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                 mPendingResults, mPendingNewIntents, mIsForward,
-                mProfilerInfo, client, mAssistToken);
+                mProfilerInfo, client, mAssistToken, mFixedRotationAdjustments);
         client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -101,14 +103,14 @@
             String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
             PersistableBundle persistentState, List<ResultInfo> pendingResults,
             List<ReferrerIntent> pendingNewIntents, boolean isForward, ProfilerInfo profilerInfo,
-            IBinder assistToken) {
+            IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments) {
         LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
         if (instance == null) {
             instance = new LaunchActivityItem();
         }
         setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer,
                 voiceInteractor, procState, state, persistentState, pendingResults,
-                pendingNewIntents, isForward, profilerInfo, assistToken);
+                pendingNewIntents, isForward, profilerInfo, assistToken, fixedRotationAdjustments);
 
         return instance;
     }
@@ -116,7 +118,7 @@
     @Override
     public void recycle() {
         setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
-                false, null, null);
+                false, null, null, null);
         ObjectPool.recycle(this);
     }
 
@@ -142,6 +144,7 @@
         dest.writeBoolean(mIsForward);
         dest.writeTypedObject(mProfilerInfo, flags);
         dest.writeStrongBinder(mAssistToken);
+        dest.writeTypedObject(mFixedRotationAdjustments, flags);
     }
 
     /** Read from Parcel. */
@@ -156,7 +159,8 @@
                 in.createTypedArrayList(ResultInfo.CREATOR),
                 in.createTypedArrayList(ReferrerIntent.CREATOR), in.readBoolean(),
                 in.readTypedObject(ProfilerInfo.CREATOR),
-                in.readStrongBinder());
+                in.readStrongBinder(),
+                in.readTypedObject(FixedRotationAdjustments.CREATOR));
     }
 
     public static final @android.annotation.NonNull Creator<LaunchActivityItem> CREATOR =
@@ -192,7 +196,8 @@
                 && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
                 && mIsForward == other.mIsForward
                 && Objects.equals(mProfilerInfo, other.mProfilerInfo)
-                && Objects.equals(mAssistToken, other.mAssistToken);
+                && Objects.equals(mAssistToken, other.mAssistToken)
+                && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments);
     }
 
     @Override
@@ -212,6 +217,7 @@
         result = 31 * result + (mIsForward ? 1 : 0);
         result = 31 * result + Objects.hashCode(mProfilerInfo);
         result = 31 * result + Objects.hashCode(mAssistToken);
+        result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
         return result;
     }
 
@@ -247,7 +253,7 @@
                 + ",referrer=" + mReferrer + ",procState=" + mProcState + ",state=" + mState
                 + ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
                 + ",pendingNewIntents=" + mPendingNewIntents + ",profilerInfo=" + mProfilerInfo
-                + " assistToken=" + mAssistToken
+                + ",assistToken=" + mAssistToken + ",rotationAdj=" + mFixedRotationAdjustments
                 + "}";
     }
 
@@ -257,7 +263,8 @@
             CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
             int procState, Bundle state, PersistableBundle persistentState,
             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
-            boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken) {
+            boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
+            FixedRotationAdjustments fixedRotationAdjustments) {
         instance.mIntent = intent;
         instance.mIdent = ident;
         instance.mInfo = info;
@@ -274,5 +281,6 @@
         instance.mIsForward = isForward;
         instance.mProfilerInfo = profilerInfo;
         instance.mAssistToken = assistToken;
+        instance.mFixedRotationAdjustments = fixedRotationAdjustments;
     }
 }
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 37baae3..0105896 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -51,6 +51,9 @@
     void uninstall(in VersionedPackage versionedPackage, String callerPackageName, int flags,
             in IntentSender statusReceiver, int userId);
 
+    void uninstallExistingPackage(in VersionedPackage versionedPackage, String callerPackageName,
+            in IntentSender statusReceiver, int userId);
+
     void installExistingPackage(String packageName, int installFlags, int installReason,
             in IntentSender statusReceiver, int userId, in List<String> whiteListedPermissions);
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 8bebaff..f257326 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -235,6 +235,16 @@
     void deletePackageVersioned(in VersionedPackage versionedPackage,
             IPackageDeleteObserver2 observer, int userId, int flags);
 
+    /**
+     * Delete a package for a specific user.
+     *
+     * @param versionedPackage The package to delete.
+     * @param observer a callback to use to notify when the package deletion in finished.
+     * @param userId the id of the user for whom to delete the package
+     */
+    void deleteExistingPackageAsUser(in VersionedPackage versionedPackage,
+            IPackageDeleteObserver2 observer, int userId);
+
     @UnsupportedAppUsage
     String getInstallerPackageName(in String packageName);
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 85a3986..ed75504 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -720,6 +720,27 @@
         }
     }
 
+    /**
+     * Uninstall the given package for the user for which this installer was created if the package
+     * will still exist for other users on the device.
+     *
+     * @param packageName The package to install.
+     * @param statusReceiver Where to deliver the result.
+     *
+     * {@hide}
+     */
+    @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
+    public void uninstallExistingPackage(@NonNull String packageName,
+            @Nullable IntentSender statusReceiver) {
+        Objects.requireNonNull(packageName, "packageName cannot be null");
+        try {
+            mInstaller.uninstallExistingPackage(
+                    new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    mInstallerPackageName, statusReceiver, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /** {@hide} */
     @SystemApi
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index c399bc7..0f1c876 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -81,6 +81,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Class for accessing an application's resources.  This sits on top of the
@@ -140,6 +141,9 @@
     @UnsupportedAppUsage
     private DrawableInflater mDrawableInflater;
 
+    /** Used to override the returned adjustments of {@link #getDisplayAdjustments}. */
+    private DisplayAdjustments mOverrideDisplayAdjustments;
+
     /** Lock object used to protect access to {@link #mTmpValue}. */
     private final Object mTmpValueLock = new Object();
 
@@ -2055,10 +2059,41 @@
     /** @hide */
     @UnsupportedAppUsage
     public DisplayAdjustments getDisplayAdjustments() {
+        final DisplayAdjustments overrideDisplayAdjustments = mOverrideDisplayAdjustments;
+        if (overrideDisplayAdjustments != null) {
+            return overrideDisplayAdjustments;
+        }
         return mResourcesImpl.getDisplayAdjustments();
     }
 
     /**
+     * Customize the display adjustments based on the current one in {@link #mResourcesImpl}, in
+     * order to isolate the effect with other instances of {@link Resource} that may share the same
+     * instance of {@link ResourcesImpl}.
+     *
+     * @param override The operation to override the existing display adjustments. If it is null,
+     *                 the override adjustments will be cleared.
+     * @hide
+     */
+    public void overrideDisplayAdjustments(@Nullable Consumer<DisplayAdjustments> override) {
+        if (override != null) {
+            mOverrideDisplayAdjustments = new DisplayAdjustments(
+                    mResourcesImpl.getDisplayAdjustments());
+            override.accept(mOverrideDisplayAdjustments);
+        } else {
+            mOverrideDisplayAdjustments = null;
+        }
+    }
+
+    /**
+     * Return {@code true} if the override display adjustments have been set.
+     * @hide
+     */
+    public boolean hasOverrideDisplayAdjustments() {
+        return mOverrideDisplayAdjustments != null;
+    }
+
+    /**
      * Return the current configuration that is in effect for this resource 
      * object.  The returned object should be treated as read-only.
      * 
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 6905f83..d071037 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2182,11 +2182,13 @@
      * <p>By using this control, the application gains a simpler way to control zoom, which can
      * be a combination of optical and digital zoom. For example, a multi-camera system may
      * contain more than one lens with different focal lengths, and the user can use optical
-     * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:
-     * <em> Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
-     *   better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.
-     * </em> Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
-     *   {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</p>
+     * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:</p>
+     * <ul>
+     * <li>Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
+     *   better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</li>
+     * <li>Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
+     *   {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</li>
+     * </ul>
      * <p>To illustrate, here are several scenarios of different zoom ratios, crop regions,
      * and output streams, for a hypothetical camera device with an active array of size
      * <code>(2000,1500)</code>.</p>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index be03502..ae04693 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2412,11 +2412,13 @@
      * <p>By using this control, the application gains a simpler way to control zoom, which can
      * be a combination of optical and digital zoom. For example, a multi-camera system may
      * contain more than one lens with different focal lengths, and the user can use optical
-     * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:
-     * <em> Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
-     *   better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.
-     * </em> Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
-     *   {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</p>
+     * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:</p>
+     * <ul>
+     * <li>Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
+     *   better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</li>
+     * <li>Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
+     *   {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</li>
+     * </ul>
      * <p>To illustrate, here are several scenarios of different zoom ratios, crop regions,
      * and output streams, for a hypothetical camera device with an active array of size
      * <code>(2000,1500)</code>.</p>
diff --git a/core/java/android/hardware/display/OWNERS b/core/java/android/hardware/display/OWNERS
new file mode 100644
index 0000000..9ca3910
--- /dev/null
+++ b/core/java/android/hardware/display/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+santoscordon@google.com
diff --git a/core/java/android/hardware/input/OWNERS b/core/java/android/hardware/input/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/core/java/android/hardware/input/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index e371df0..0ec4fb8 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,3 +1,20 @@
+# Haptics
+per-file ExternalVibration.aidl = michaelwr@google.com
+per-file ExternalVibration.java = michaelwr@google.com
+per-file IExternalVibrationController.aidl = michaelwr@google.com
+per-file IExternalVibratorService.aidl = michaelwr@google.com
+per-file IVibratorService.aidl = michaelwr@google.com
+per-file NullVibrator.java = michaelwr@google.com
+per-file SystemVibrator.java = michaelwr@google.com
+per-file VibrationEffect.aidl = michaelwr@google.com
+per-file VibrationEffect.java = michaelwr@google.com
+per-file Vibrator.java = michaelwr@google.com
+
+# PowerManager
+per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
+per-file PowerManager.java = michaelwr@google.com, santoscordon@google.com
+per-file PowerManagerInternal.java = michaelwr@google.com, santoscordon@google.com
+
 # Zygote
 per-file ZygoteProcess.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 236ea00..25bf430 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2582,8 +2582,8 @@
     }
 
     /**
-     * Creates a user with the specified name and options. For non-admin users, default user
-     * restrictions are going to be applied.
+     * Creates a user with the specified name and options.
+     * Default user restrictions will be applied.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
      * @param name the user's name
@@ -2602,8 +2602,8 @@
     }
 
     /**
-     * Creates a user with the specified name and options. For non-admin users, default user
-     * restrictions will be applied.
+     * Creates a user with the specified name and options.
+     * Default user restrictions will be applied.
      *
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
      * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
@@ -2637,8 +2637,7 @@
     }
 
     /**
-     * Pre-creates a user of the specified type. For non-admin users, default user
-     * restrictions will be applied.
+     * Pre-creates a user of the specified type. Default user restrictions will be applied.
      *
      * <p>This method can be used by OEMs to "warm" up the user creation by pre-creating some users
      * at the first boot, so they when the "real" user is created (for example,
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 06caa03..dea932d 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -56,15 +56,11 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.RemoteStream;
 import com.android.internal.infra.ServiceConnector;
-import com.android.internal.os.TransferPipe;
 import com.android.internal.util.CollectionUtils;
 
 import libcore.util.EmptyArray;
 
 import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -72,7 +68,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -88,6 +83,7 @@
 public final class PermissionControllerManager {
     private static final String TAG = PermissionControllerManager.class.getSimpleName();
 
+    private static final long REQUEST_TIMEOUT_MILLIS = 60000;
     private static final long UNBIND_TIMEOUT_MILLIS = 10000;
     private static final int CHUNK_SIZE = 4 * 1024;
 
@@ -227,6 +223,11 @@
                     }
 
                     @Override
+                    protected long getRequestTimeoutMs() {
+                        return REQUEST_TIMEOUT_MILLIS;
+                    }
+
+                    @Override
                     protected long getAutoDisconnectTimeoutMs() {
                         return UNBIND_TIMEOUT_MILLIS;
                     }
@@ -487,28 +488,12 @@
      *
      * @hide
      */
-    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
-        CompletableFuture<Throwable> dumpResult = new CompletableFuture<>();
-        mRemoteService.postForResult(
-                service -> TransferPipe.dumpAsync(service.asBinder(), args))
-                .whenComplete(
-                        (dump, err) -> {
-                            try (FileOutputStream out = new FileOutputStream(fd)) {
-                                out.write(dump);
-                            } catch (IOException | NullPointerException e) {
-                                Log.e(TAG, "Could for forwards permission controller dump", e);
-                            }
-
-                            dumpResult.complete(err);
-                        });
-
+    public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) {
         try {
-            Throwable err = dumpResult.get(UNBIND_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-            if (err != null) {
-                throw err;
-            }
-        } catch (Throwable e) {
-            Log.e(TAG, "Could not dump permission controller state", e);
+            mRemoteService.post(service -> service.asBinder().dump(fd, args))
+                    .get(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not get dump", e);
         }
     }
 
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index c6ede32..8ad35e7 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -499,6 +499,11 @@
 
             @Override
             protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+                checkNotNull(fd, "fd");
+                checkNotNull(writer, "writer");
+
+                enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
+
                 PermissionControllerService.this.dump(fd, writer, args);
             }
         };
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index d94160c..62becc5 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -86,6 +86,16 @@
      */
     public static final @RequestFlags int FLAG_PASSWORD_INPUT_TYPE = 0x4;
 
+    /**
+     * Indicates the view was not focused.
+     *
+     * <p><b>Note:</b> Defines the flag value to 0x10, because the flag value 0x08 has been defined
+     * in {@link AutofillManager}.</p>
+     *
+     * @hide
+     */
+    public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
+
     /** @hide */
     public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
 
@@ -165,7 +175,8 @@
     @IntDef(flag = true, prefix = "FLAG_", value = {
         FLAG_MANUAL_REQUEST,
         FLAG_COMPATIBILITY_MODE_REQUEST,
-        FLAG_PASSWORD_INPUT_TYPE
+        FLAG_PASSWORD_INPUT_TYPE,
+        FLAG_VIEW_NOT_FOCUSED
     })
     @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
@@ -187,6 +198,8 @@
                     return "FLAG_COMPATIBILITY_MODE_REQUEST";
             case FLAG_PASSWORD_INPUT_TYPE:
                     return "FLAG_PASSWORD_INPUT_TYPE";
+            case FLAG_VIEW_NOT_FOCUSED:
+                    return "FLAG_VIEW_NOT_FOCUSED";
             default: return Integer.toHexString(value);
         }
     }
@@ -248,7 +261,8 @@
                 mFlags,
                 FLAG_MANUAL_REQUEST
                         | FLAG_COMPATIBILITY_MODE_REQUEST
-                        | FLAG_PASSWORD_INPUT_TYPE);
+                        | FLAG_PASSWORD_INPUT_TYPE
+                        | FLAG_VIEW_NOT_FOCUSED);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
 
         onConstructed();
@@ -384,7 +398,8 @@
                 mFlags,
                 FLAG_MANUAL_REQUEST
                         | FLAG_COMPATIBILITY_MODE_REQUEST
-                        | FLAG_PASSWORD_INPUT_TYPE);
+                        | FLAG_PASSWORD_INPUT_TYPE
+                        | FLAG_VIEW_NOT_FOCUSED);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
 
         onConstructed();
@@ -405,10 +420,10 @@
     };
 
     @DataClass.Generated(
-            time = 1588119440090L,
+            time = 1589280816805L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
-            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4469fdb..8db1703 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -104,6 +104,14 @@
     private int mCachedAppHeightCompat;
 
     /**
+     * Indicates that the application is started in a different rotation than the real display, so
+     * the display information may be adjusted. That ensures the methods {@link #getRotation},
+     * {@link #getRealSize}, {@link #getRealMetrics}, and {@link #getCutout} are consistent with how
+     * the application window is laid out.
+     */
+    private boolean mMayAdjustByFixedRotation;
+
+    /**
      * The default Display id, which is the id of the primary display assuming there is one.
      */
     public static final int DEFAULT_DISPLAY = 0;
@@ -804,7 +812,9 @@
     public int getRotation() {
         synchronized (this) {
             updateDisplayInfoLocked();
-            return mDisplayInfo.rotation;
+            return mMayAdjustByFixedRotation
+                    ? getDisplayAdjustments().getRotation(mDisplayInfo.rotation)
+                    : mDisplayInfo.rotation;
         }
     }
 
@@ -828,7 +838,9 @@
     public DisplayCutout getCutout() {
         synchronized (this) {
             updateDisplayInfoLocked();
-            return mDisplayInfo.displayCutout;
+            return mMayAdjustByFixedRotation
+                    ? getDisplayAdjustments().getDisplayCutout(mDisplayInfo.displayCutout)
+                    : mDisplayInfo.displayCutout;
         }
     }
 
@@ -1140,6 +1152,9 @@
             updateDisplayInfoLocked();
             outSize.x = mDisplayInfo.logicalWidth;
             outSize.y = mDisplayInfo.logicalHeight;
+            if (mMayAdjustByFixedRotation) {
+                getDisplayAdjustments().adjustSize(outSize, mDisplayInfo.rotation);
+            }
         }
     }
 
@@ -1159,6 +1174,9 @@
             updateDisplayInfoLocked();
             mDisplayInfo.getLogicalMetrics(outMetrics,
                     CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+            if (mMayAdjustByFixedRotation) {
+                getDisplayAdjustments().adjustMetrics(outMetrics, mDisplayInfo.rotation);
+            }
         }
     }
 
@@ -1225,6 +1243,9 @@
                 }
             }
         }
+
+        mMayAdjustByFixedRotation = mResources != null
+                && mResources.hasOverrideDisplayAdjustments();
     }
 
     private void updateCachedAppSizeIfNeededLocked() {
@@ -1243,9 +1264,12 @@
     public String toString() {
         synchronized (this) {
             updateDisplayInfoLocked();
-            mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+            final DisplayAdjustments adjustments = getDisplayAdjustments();
+            mDisplayInfo.getAppMetrics(mTempMetrics, adjustments);
             return "Display id " + mDisplayId + ": " + mDisplayInfo
-                    + ", " + mTempMetrics + ", isValid=" + mIsValid;
+                    + (mMayAdjustByFixedRotation
+                            ? (", " + adjustments.getFixedRotationAdjustments() + ", ") : ", ")
+                    + mTempMetrics + ", isValid=" + mIsValid;
         }
     }
 
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index 27c2d5c..c726bee 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -21,6 +21,10 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayMetrics;
 
 import java.util.Objects;
 
@@ -30,6 +34,7 @@
 
     private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
     private final Configuration mConfiguration = new Configuration(Configuration.EMPTY);
+    private FixedRotationAdjustments mFixedRotationAdjustments;
 
     @UnsupportedAppUsage
     public DisplayAdjustments() {
@@ -44,6 +49,7 @@
     public DisplayAdjustments(@NonNull DisplayAdjustments daj) {
         setCompatibilityInfo(daj.mCompatInfo);
         mConfiguration.setTo(daj.getConfiguration());
+        mFixedRotationAdjustments = daj.mFixedRotationAdjustments;
     }
 
     @UnsupportedAppUsage
@@ -84,11 +90,78 @@
         return mConfiguration;
     }
 
+    public void setFixedRotationAdjustments(FixedRotationAdjustments fixedRotationAdjustments) {
+        mFixedRotationAdjustments = fixedRotationAdjustments;
+    }
+
+    public FixedRotationAdjustments getFixedRotationAdjustments() {
+        return mFixedRotationAdjustments;
+    }
+
+    /** Returns {@code false} if the width and height of display should swap. */
+    private boolean noFlip(@Surface.Rotation int realRotation) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        if (rotationAdjustments == null) {
+            return true;
+        }
+        // Check if the delta is rotated by 90 degrees.
+        return (realRotation - rotationAdjustments.mRotation + 4) % 2 == 0;
+    }
+
+    /** Adjusts the given size if possible. */
+    public void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation) {
+        if (noFlip(realRotation)) {
+            return;
+        }
+        final int w = size.x;
+        size.x = size.y;
+        size.y = w;
+    }
+
+    /** Adjusts the given metrics if possible. */
+    public void adjustMetrics(@NonNull DisplayMetrics metrics, @Surface.Rotation int realRotation) {
+        if (noFlip(realRotation)) {
+            return;
+        }
+        int w = metrics.widthPixels;
+        metrics.widthPixels = metrics.heightPixels;
+        metrics.heightPixels = w;
+
+        w = metrics.noncompatWidthPixels;
+        metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
+        metrics.noncompatHeightPixels = w;
+
+        float x = metrics.xdpi;
+        metrics.xdpi = metrics.ydpi;
+        metrics.ydpi = x;
+
+        x = metrics.noncompatXdpi;
+        metrics.noncompatXdpi = metrics.noncompatYdpi;
+        metrics.noncompatYdpi = x;
+    }
+
+    /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
+    @Nullable
+    public DisplayCutout getDisplayCutout(@Nullable DisplayCutout realCutout) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        return rotationAdjustments != null && rotationAdjustments.mRotatedDisplayCutout != null
+                ? rotationAdjustments.mRotatedDisplayCutout
+                : realCutout;
+    }
+
+    /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */
+    @Surface.Rotation
+    public int getRotation(@Surface.Rotation int realRotation) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        return rotationAdjustments != null ? rotationAdjustments.mRotation : realRotation;
+    }
+
     @Override
     public int hashCode() {
         int hash = 17;
         hash = hash * 31 + Objects.hashCode(mCompatInfo);
         hash = hash * 31 + Objects.hashCode(mConfiguration);
+        hash = hash * 31 + Objects.hashCode(mFixedRotationAdjustments);
         return hash;
     }
 
@@ -98,7 +171,82 @@
             return false;
         }
         DisplayAdjustments daj = (DisplayAdjustments)o;
-        return Objects.equals(daj.mCompatInfo, mCompatInfo) &&
-                Objects.equals(daj.mConfiguration, mConfiguration);
+        return Objects.equals(daj.mCompatInfo, mCompatInfo)
+                && Objects.equals(daj.mConfiguration, mConfiguration)
+                && Objects.equals(daj.mFixedRotationAdjustments, mFixedRotationAdjustments);
+    }
+
+    /**
+     * An application can be launched in different rotation than the real display. This class
+     * provides the information to adjust the values returned by {@link #Display}.
+     * @hide
+     */
+    public static class FixedRotationAdjustments implements Parcelable {
+        /** The application-based rotation. */
+        @Surface.Rotation
+        final int mRotation;
+
+        /** Non-null if the device has cutout. */
+        @Nullable
+        final DisplayCutout mRotatedDisplayCutout;
+
+        public FixedRotationAdjustments(@Surface.Rotation int rotation, DisplayCutout cutout) {
+            mRotation = rotation;
+            mRotatedDisplayCutout = cutout;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 17;
+            hash = hash * 31 + mRotation;
+            hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof FixedRotationAdjustments)) {
+                return false;
+            }
+            final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
+            return mRotation == other.mRotation
+                    && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
+        }
+
+        @Override
+        public String toString() {
+            return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
+                    + " cutout=" + mRotatedDisplayCutout + "}";
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mRotation);
+            dest.writeTypedObject(
+                    new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
+        }
+
+        private FixedRotationAdjustments(Parcel in) {
+            mRotation = in.readInt();
+            final DisplayCutout.ParcelableWrapper cutoutWrapper =
+                    in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
+            mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
+        }
+
+        public static final Creator<FixedRotationAdjustments> CREATOR =
+                new Creator<FixedRotationAdjustments>() {
+            public FixedRotationAdjustments createFromParcel(Parcel in) {
+                return new FixedRotationAdjustments(in);
+            }
+
+            public FixedRotationAdjustments[] newArray(int size) {
+                return new FixedRotationAdjustments[size];
+            }
+        };
     }
 }
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
new file mode 100644
index 0000000..7b60f2e
--- /dev/null
+++ b/core/java/android/view/OWNERS
@@ -0,0 +1,15 @@
+# Display
+per-file Display.java = michaelwr@google.com, santoscordon@google.com
+per-file DisplayInfo.java = michaelwr@google.com, santoscordon@google.com
+
+# Haptics
+per-file HapticFeedbackConstants.java = michaelwr@google.com, santoscordon@google.com
+
+# Input
+per-file IInputMonitorHost.aidl = michaelwr@google.com, svv@google.com
+per-file Input*.java = michaelwr@google.com, svv@google.com
+per-file Input*.aidl = michaelwr@google.com, svv@google.com
+per-file KeyEvent.java = michaelwr@google.com, svv@google.com
+per-file MotionEvent.java = michaelwr@google.com, svv@google.com
+per-file PointerIcon.java = michaelwr@google.com, svv@google.com
+per-file SimulatedDpad.java = michaelwr@google.com, svv@google.com
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a660218..1d56154 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -220,6 +220,8 @@
 
     private static native long nativeAcquireFrameRateFlexibilityToken();
     private static native void nativeReleaseFrameRateFlexibilityToken(long token);
+    private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject,
+            int transformHint);
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private String mName;
@@ -2310,6 +2312,39 @@
         }
 
         /**
+         * Provide the graphic producer a transform hint if the layer and its children are
+         * in an orientation different from the display's orientation. The caller is responsible
+         * for clearing this transform hint if the layer is no longer in a fixed orientation.
+         *
+         * The transform hint is used to prevent allocating a buffer of different size when a
+         * layer is rotated. The producer can choose to consume the hint and allocate the buffer
+         * with the same size.
+         *
+         * @return This Transaction.
+         * @hide
+         */
+        @NonNull
+        public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
+                       @Surface.Rotation int transformHint) {
+            checkPreconditions(sc);
+            nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
+            return this;
+        }
+
+        /**
+         * Clearing any transform hint if set on this layer.
+         *
+         * @return This Transaction.
+         * @hide
+         */
+        @NonNull
+        public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
+            checkPreconditions(sc);
+            nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
+            return this;
+        }
+
+        /**
          * Set the Z-order for a given SurfaceControl, relative to it's siblings.
          * If two siblings share the same Z order the ordering is undefined. Surfaces
          * with a negative Z will be placed below the parent surface.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 76fe6b5f..553e3c8 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -18,6 +18,7 @@
 
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
 import static android.view.autofill.Helper.sDebug;
 import static android.view.autofill.Helper.sVerbose;
 import static android.view.autofill.Helper.toList;
@@ -879,7 +880,11 @@
      * @param view view requesting the new autofill context.
      */
     public void requestAutofill(@NonNull View view) {
-        notifyViewEntered(view, FLAG_MANUAL_REQUEST);
+        int flags = FLAG_MANUAL_REQUEST;
+        if (!view.isFocused()) {
+            flags |= FLAG_VIEW_NOT_FOCUSED;
+        }
+        notifyViewEntered(view, flags);
     }
 
     /**
@@ -926,7 +931,11 @@
      * @param absBounds absolute boundaries of the virtual view in the screen.
      */
     public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
-        notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST);
+        int flags = FLAG_MANUAL_REQUEST;
+        if (!view.isFocused()) {
+            flags |= FLAG_VIEW_NOT_FOCUSED;
+        }
+        notifyViewEntered(view, virtualId, absBounds, flags);
     }
 
     /**
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 35dd576..e224e84 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1118,6 +1118,9 @@
      * {@link #setAppCachePath}.
      *
      * @param flag {@code true} if the WebView should enable Application Caches
+     * @deprecated The Application Cache API is deprecated and this method will
+     *             become a no-op on all Android versions once support is
+     *             removed in Chromium. Consider using Service Workers instead.
      */
     public abstract void setAppCacheEnabled(boolean flag);
 
@@ -1130,6 +1133,9 @@
      * @param appCachePath a String path to the directory containing
      *                     Application Caches files.
      * @see #setAppCacheEnabled
+     * @deprecated The Application Cache API is deprecated and this method will
+     *             become a no-op on all Android versions once support is
+     *             removed in Chromium. Consider using Service Workers instead.
      */
     public abstract void setAppCachePath(String appCachePath);
 
@@ -1142,7 +1148,7 @@
      * It is recommended to leave the maximum size set to the default value.
      *
      * @param appCacheMaxSize the maximum size in bytes
-     * @deprecated In future quota will be managed automatically.
+     * @deprecated Quota is managed automatically; this method is a no-op.
      */
     @Deprecated
     public abstract void setAppCacheMaxSize(long appCacheMaxSize);
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 891d535..a3c29a8 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.internal.util.ArrayUtils.convertToLongArray;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -52,6 +53,7 @@
 import android.widget.Toast;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.lang.annotation.Retention;
@@ -267,16 +269,21 @@
     }
 
     private AlertDialog createShortcutWarningDialog(int userId) {
-        final String warningMessage = mContext.getString(
-                R.string.accessibility_shortcut_toogle_warning);
+        List<AccessibilityTarget> targets = getTargets(mContext, ACCESSIBILITY_SHORTCUT_KEY);
+        if (targets.size() == 0) {
+            return null;
+        }
+
+        // Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
+        // Put "don't turn on" as the primary action.
         final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
                 // Use SystemUI context so we pick up any theme set in a vendor overlay
                 mFrameworkObjectProvider.getSystemUiContext())
-                .setTitle(R.string.accessibility_shortcut_warning_dialog_title)
-                .setMessage(warningMessage)
+                .setTitle(getShortcutWarningTitle(targets))
+                .setMessage(getShortcutWarningMessage(targets))
                 .setCancelable(false)
-                .setPositiveButton(R.string.leave_accessibility_shortcut_on, null)
-                .setNegativeButton(R.string.disable_accessibility_shortcut,
+                .setNegativeButton(R.string.accessibility_shortcut_on, null)
+                .setPositiveButton(R.string.accessibility_shortcut_off,
                         (DialogInterface d, int which) -> {
                             Settings.Secure.putStringForUser(mContext.getContentResolver(),
                                     Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
@@ -297,6 +304,32 @@
         return alertDialog;
     }
 
+    private String getShortcutWarningTitle(List<AccessibilityTarget> targets) {
+        if (targets.size() == 1) {
+            return mContext.getString(
+                    R.string.accessibility_shortcut_single_service_warning_title,
+                    targets.get(0).getLabel());
+        }
+        return mContext.getString(
+                R.string.accessibility_shortcut_multiple_service_warning_title);
+    }
+
+    private String getShortcutWarningMessage(List<AccessibilityTarget> targets) {
+        if (targets.size() == 1) {
+            return mContext.getString(
+                    R.string.accessibility_shortcut_single_service_warning,
+                    targets.get(0).getLabel());
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        for (AccessibilityTarget target : targets) {
+            sb.append(mContext.getString(R.string.accessibility_shortcut_multiple_service_list,
+                    target.getLabel()));
+        }
+        return mContext.getString(R.string.accessibility_shortcut_multiple_service_warning,
+                sb.toString());
+    }
+
     private AccessibilityServiceInfo getInfoForTargetService() {
         final ComponentName targetComponentName = getShortcutTargetComponentName();
         if (targetComponentName == null) {
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index 37871d0..d756593 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -38,7 +38,7 @@
  * Abstract base class for creating various target related to accessibility service,
  * accessibility activity, and white listing feature.
  */
-abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
+public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
         OnTargetCheckedChangeListener {
     private Context mContext;
     @ShortcutType
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index f63cbe0..60a102a 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -53,19 +53,61 @@
 /**
  * Collection of utilities for accessibility target.
  */
-final class AccessibilityTargetHelper {
+public final class AccessibilityTargetHelper {
     private AccessibilityTargetHelper() {}
 
-    static List<AccessibilityTarget> getTargets(Context context,
+    /**
+     * Returns list of {@link AccessibilityTarget} of assigned accessibility shortcuts from
+     * {@link AccessibilityManager#getAccessibilityShortcutTargets} including accessibility
+     * feature's package name, component id, etc.
+     *
+     * @param context The context of the application.
+     * @param shortcutType The shortcut type.
+     * @return The list of {@link AccessibilityTarget}.
+     * @hide
+     */
+    public static List<AccessibilityTarget> getTargets(Context context,
             @ShortcutType int shortcutType) {
-        final List<AccessibilityTarget> targets = getInstalledTargets(context, shortcutType);
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
-        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
-        targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+        // List all accessibility target
+        final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
+                shortcutType);
 
-        return targets;
+        // List accessibility shortcut target
+        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        final List<String> assignedTargets = am.getAccessibilityShortcutTargets(shortcutType);
+
+        // Get the list of accessibility shortcut target in all accessibility target
+        final List<AccessibilityTarget> results = new ArrayList<>();
+        for (String assignedTarget : assignedTargets) {
+            for (AccessibilityTarget installedTarget : installedTargets) {
+                if (!MAGNIFICATION_CONTROLLER_NAME.contentEquals(assignedTarget)) {
+                    final ComponentName assignedTargetComponentName =
+                            ComponentName.unflattenFromString(assignedTarget);
+                    final ComponentName targetComponentName = ComponentName.unflattenFromString(
+                            installedTarget.getId());
+                    if (assignedTargetComponentName.equals(targetComponentName)) {
+                        results.add(installedTarget);
+                        continue;
+                    }
+                }
+                if (assignedTarget.contentEquals(installedTarget.getId())) {
+                    results.add(installedTarget);
+                }
+            }
+        }
+        return results;
     }
 
+    /**
+     * Returns list of {@link AccessibilityTarget} of the installed accessibility service,
+     * accessibility activity, and white listing feature including accessibility feature's package
+     * name, component id, etc.
+     *
+     * @param context The context of the application.
+     * @param shortcutType The shortcut type.
+     * @return The list of {@link AccessibilityTarget}.
+     */
     static List<AccessibilityTarget> getInstalledTargets(Context context,
             @ShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
@@ -110,9 +152,10 @@
 
     private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
             @ShortcutType int shortcutType) {
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
+        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> installedServices =
-                ams.getInstalledAccessibilityServiceList();
+                am.getInstalledAccessibilityServiceList();
         if (installedServices == null) {
             return Collections.emptyList();
         }
@@ -136,9 +179,10 @@
 
     private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
             @ShortcutType int shortcutType) {
-        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
+        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityShortcutInfo> installedServices =
-                ams.getInstalledAccessibilityShortcutListAsUser(context,
+                am.getInstalledAccessibilityShortcutListAsUser(context,
                         ActivityManager.getCurrentUser());
         if (installedServices == null) {
             return Collections.emptyList();
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index e50b010..9ee0b0e 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -142,7 +142,8 @@
      */
     public static boolean isAccessibilityServiceEnabled(Context context,
             @NonNull String componentId) {
-        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> enabledServices =
                 am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
 
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 100422f..31ccb6c3 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -137,7 +137,8 @@
      */
     public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
             @NonNull String componentId) {
-        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
         final List<String> requiredTargets = am.getAccessibilityShortcutTargets(shortcutType);
         return requiredTargets.contains(componentId);
     }
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 2f048c9..a50a522 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -68,6 +68,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Predicate;
 import java.util.regex.Pattern;
 
 /**
@@ -381,17 +382,51 @@
         return result;
     }
 
+    /**
+     * This method is similar to
+     * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
+     * all children documents including hidden directories/files.
+     *
+     * <p>
+     * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
+     * reasons. This method may show privacy sensitive data, so its usage should only be in
+     * restricted modes.
+     *
+     * @param parentDocumentId the directory to return children for.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @param sortOrder how to order the rows, formatted as an SQL
+     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
+     *            Passing {@code null} will use the default sort order, which
+     *            may be unordered. This ordering is a hint that can be used to
+     *            prioritize how data is fetched from the network, but UI may
+     *            always enforce a specific ordering
+     * @throws FileNotFoundException when parent document doesn't exist or query fails
+     */
+    protected Cursor queryChildDocumentsShowAll(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+        return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
+    }
+
     @Override
     public Cursor queryChildDocuments(
             String parentDocumentId, String[] projection, String sortOrder)
             throws FileNotFoundException {
+        // Access to some directories is hidden for privacy reasons.
+        return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+    }
 
+    private Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder,
+            @NonNull Predicate<File> filter) throws FileNotFoundException {
         final File parent = getFileForDocId(parentDocumentId);
         final MatrixCursor result = new DirectoryCursor(
                 resolveProjection(projection), parentDocumentId, parent);
         if (parent.isDirectory()) {
             for (File file : FileUtils.listFilesOrEmpty(parent)) {
-                if (!shouldHide(file)) {
+                if (filter.test(file)) {
                     includeFile(result, null, file);
                 }
             }
@@ -617,6 +652,10 @@
         return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
     }
 
+    private boolean shouldShow(@NonNull File file) {
+        return !shouldHide(file);
+    }
+
     protected boolean shouldBlockFromTree(@NonNull String docId) {
         return false;
     }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0a7a6e5..d816285 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1380,7 +1380,7 @@
 
     public void registerStrongAuthTracker(final StrongAuthTracker strongAuthTracker) {
         try {
-            getLockSettings().registerStrongAuthTracker(strongAuthTracker.mStub);
+            getLockSettings().registerStrongAuthTracker(strongAuthTracker.getStub());
         } catch (RemoteException e) {
             throw new RuntimeException("Could not register StrongAuthTracker");
         }
@@ -1388,7 +1388,7 @@
 
     public void unregisterStrongAuthTracker(final StrongAuthTracker strongAuthTracker) {
         try {
-            getLockSettings().unregisterStrongAuthTracker(strongAuthTracker.mStub);
+            getLockSettings().unregisterStrongAuthTracker(strongAuthTracker.getStub());
         } catch (RemoteException e) {
             Log.e(TAG, "Could not unregister StrongAuthTracker", e);
         }
@@ -1751,7 +1751,7 @@
             }
         }
 
-        protected final IStrongAuthTracker.Stub mStub = new IStrongAuthTracker.Stub() {
+        private final IStrongAuthTracker.Stub mStub = new IStrongAuthTracker.Stub() {
             @Override
             public void onStrongAuthRequiredChanged(@StrongAuthFlags int strongAuthFlags,
                     int userId) {
@@ -1766,6 +1766,10 @@
             }
         };
 
+        public IStrongAuthTracker.Stub getStub() {
+            return mStub;
+        }
+
         private class H extends Handler {
             static final int MSG_ON_STRONG_AUTH_REQUIRED_CHANGED = 1;
             static final int MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = 2;
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
new file mode 100644
index 0000000..cca39ea
--- /dev/null
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -0,0 +1 @@
+per-file PointerLocationView.java = michaelwr@google.com, svv@google.com
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 7ff15f2..d7d8621 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -5,5 +5,16 @@
 # Connectivity
 per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
 
+# Display
+per-file android_hardware_display_* = michaelwr@google.com, santoscordon@google.com
+
+# Input
+per-file android_hardware_input* = michaelwr@google.com, svv@google.com
+per-file android_view_Input* = michaelwr@google.com, svv@google.com
+per-file android_view_KeyCharacterMap.* = michaelwr@google.com, svv@google.com
+per-file android_view_*KeyEvent.* = michaelwr@google.com, svv@google.com
+per-file android_view_*MotionEvent.* = michaelwr@google.com, svv@google.com
+per-file android_view_PointerIcon.* = michaelwr@google.com, svv@google.com
+
 # Zygote
 per-file com_android_internal_os_Zygote.*,fd_utils.* = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 36b4b6a..e553a78 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -645,6 +645,14 @@
     token->decStrong((void*)nativeAcquireFrameRateFlexibilityToken);
 }
 
+static void nativeSetFixedTransformHint(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                        jlong nativeObject, jint transformHint) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setFixedTransformHint(ctrl, transformHint);
+}
+
 static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
     const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
     jlongArray array = env->NewLongArray(displayIds.size());
@@ -1644,6 +1652,7 @@
             (void*)nativeSetGlobalShadowSettings },
     {"nativeGetHandle", "(J)J",
             (void*)nativeGetHandle },
+    {"nativeSetFixedTransformHint", "(JJI)V", (void*)nativeSetFixedTransformHint},
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/res/res/values-mcc334-mnc020/config.xml b/core/res/res/values-mcc334-mnc020/config.xml
index 4aad534..82b3ee6 100644
--- a/core/res/res/values-mcc334-mnc020/config.xml
+++ b/core/res/res/values-mcc334-mnc020/config.xml
@@ -19,6 +19,6 @@
 <resources>
     <bool name="config_use_sim_language_file">false</bool>
 
-    <bool name="config_pdp_rejeect_enable_retry">true</bool>
+    <bool name="config_pdp_reject_enable_retry">true</bool>
     <integer name="config_pdp_reject_retry_delay_ms">45000</integer>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d1a1534..39215e6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4392,12 +4392,6 @@
     <!-- Used in multiple service warning to list current features. [CHAR LIMIT=none] -->
     <string name="accessibility_shortcut_multiple_service_list">\t• <xliff:g id="service" example="TalkBack">%1$s</xliff:g>\n</string>
 
-    <!-- Dialog title for dialog shown when the TalkBack shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] -->
-    <string name="accessibility_shortcut_talkback_warning_title">Turn on TalkBack?</string>
-
-    <!-- Message shown in dialog when user is in the process of enabling the TalkBack via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
-    <string name="accessibility_shortcut_talkback_warning">Holding down both volume keys for a few seconds turns on TalkBack, a screen reader that is helpful for people who are blind or have low vision. TalkBack completely changes how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility.</string>
-
     <!-- Dialog title for dialog shown when this accessibility shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] -->
     <string name="accessibility_shortcut_single_service_warning_title">Turn on <xliff:g id="service" example="TalkBack">%1$s</xliff:g>?</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8d8ab58..23e553d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3239,12 +3239,15 @@
   <java-symbol type="integer" name="config_debugSystemServerPssThresholdBytes" />
 
   <!-- Accessibility Shortcut -->
-  <java-symbol type="string" name="accessibility_shortcut_warning_dialog_title" />
-  <java-symbol type="string" name="accessibility_shortcut_toogle_warning" />
+  <java-symbol type="string" name="accessibility_shortcut_single_service_warning_title" />
+  <java-symbol type="string" name="accessibility_shortcut_single_service_warning" />
+  <java-symbol type="string" name="accessibility_shortcut_multiple_service_warning_title" />
+  <java-symbol type="string" name="accessibility_shortcut_multiple_service_warning" />
+  <java-symbol type="string" name="accessibility_shortcut_multiple_service_list" />
+  <java-symbol type="string" name="accessibility_shortcut_on" />
+  <java-symbol type="string" name="accessibility_shortcut_off" />
   <java-symbol type="string" name="accessibility_shortcut_enabling_service" />
   <java-symbol type="string" name="accessibility_shortcut_disabling_service" />
-  <java-symbol type="string" name="disable_accessibility_shortcut" />
-  <java-symbol type="string" name="leave_accessibility_shortcut_on" />
   <java-symbol type="string" name="color_inversion_feature_name" />
   <java-symbol type="string" name="color_correction_feature_name" />
   <java-symbol type="string" name="config_defaultAccessibilityService" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 107fe3f..6c23125 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -144,11 +144,12 @@
         IBinder assistToken = new Binder();
 
         LaunchActivityItem emptyItem = LaunchActivityItem.obtain(null, 0, null, null, null, null,
-                null, null, 0, null, null, null, null, false, null, null);
+                null, null, 0, null, null, null, null, false, null, null, null);
         LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
                 config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
                 procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
-                true /* isForward */, null /* profilerInfo */, assistToken);
+                true /* isForward */, null /* profilerInfo */, assistToken,
+                null /* fixedRotationAdjustments */);
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
 
@@ -158,7 +159,8 @@
         LaunchActivityItem item2 = LaunchActivityItem.obtain(intent, ident, activityInfo,
                 config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
                 procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
-                true /* isForward */, null /* profilerInfo */, assistToken);
+                true /* isForward */, null /* profilerInfo */, assistToken,
+                null /* fixedRotationAdjustments */);
         assertSame(item, item2);
         assertFalse(item2.equals(emptyItem));
     }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 09ea1b1..3c32c71 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -267,7 +267,7 @@
                 null /* voiceInteractor */, 0 /* procState */, null /* state */,
                 null /* persistentState */, null /* pendingResults */,
                 null /* pendingNewIntents */, false /* isForward */, null /* profilerInfo */,
-                null /* assistToken*/));
+                null /* assistToken */, null /* fixedRotationAdjustments */));
         launchTransaction.addCallback(launchItem);
         mExecutor.execute(launchTransaction);
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 47f9323..3f8d9ef 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -52,6 +52,9 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.view.DisplayCutout;
+import android.view.Surface;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -187,11 +190,14 @@
         bundle.putParcelable("data", new ParcelableData(1));
         PersistableBundle persistableBundle = new PersistableBundle();
         persistableBundle.putInt("k", 4);
+        FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments(
+                Surface.ROTATION_90, DisplayCutout.NO_CUTOUT);
 
         LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
                 config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
                 procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
-                true /* isForward */, null /* profilerInfo */, new Binder());
+                true /* isForward */, null /* profilerInfo */, new Binder(),
+                fixedRotationAdjustments);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -340,6 +346,22 @@
         assertTrue(transaction.equals(result));
     }
 
+    @Test
+    public void testFixedRotationAdjustments() {
+        ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(),
+                null /* activityToken */);
+        transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(),
+                new FixedRotationAdjustments(Surface.ROTATION_270, DisplayCutout.NO_CUTOUT)));
+
+        writeAndPrepareForReading(transaction);
+
+        // Read from parcel and assert
+        ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(transaction.hashCode(), result.hashCode());
+        assertTrue(transaction.equals(result));
+    }
+
     /** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
     private void writeAndPrepareForReading(Parcelable parcelable) {
         parcelable.writeToParcel(mParcel, 0 /* flags */);
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
new file mode 100644
index 0000000..4d04a7a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.app.usage;
+
+import static junit.framework.Assert.fail;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+/**
+ * These tests verify that all fields defined in {@link UsageStats} and {@link UsageEvents.Event}
+ * are all known fields. This ensures that newly added fields or refactorings are accounted for in
+ * the usagestatsservice.proto and usagestatsservice_v2.proto files.
+ *
+ * Note: verification for {@link com.android.server.usage.IntervalStats} fields is located in
+ * {@link com.android.server.usage.IntervalStatsTests}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UsageStatsPersistenceTest {
+
+    // All fields in this list are defined in UsageStats and persisted - please ensure they're
+    // defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto
+    private static final String[] USAGESTATS_PERSISTED_FIELDS = {"mBeginTimeStamp", "mEndTimeStamp",
+            "mPackageName", "mPackageToken", "mLastEvent", "mAppLaunchCount", "mChooserCounts",
+            "mLastTimeUsed", "mTotalTimeInForeground", "mLastTimeForegroundServiceUsed",
+            "mTotalTimeForegroundServiceUsed", "mLastTimeVisible", "mTotalTimeVisible"};
+    // All fields in this list are defined in UsageStats but not persisted
+    private static final String[] USAGESTATS_IGNORED_FIELDS = {"CREATOR", "mActivities",
+            "mForegroundServices", "mLaunchCount", "mChooserCountsObfuscated"};
+
+    @Test
+    public void testUsageStatsFields() {
+        final UsageStats stats = new UsageStats();
+        final Field[] fields = stats.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            if (!(ArrayUtils.contains(USAGESTATS_PERSISTED_FIELDS, field.getName())
+                    || ArrayUtils.contains(USAGESTATS_IGNORED_FIELDS, field.getName()))) {
+                fail("Found an unknown field: " + field.getName() + ". Please correctly update "
+                        + "either USAGESTATS_PERSISTED_FIELDS or USAGESTATS_IGNORED_FIELDS.");
+            }
+        }
+    }
+
+    // All fields in this list are defined in UsageEvents.Event and persisted - please ensure
+    // they're defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto
+    private static final String[] USAGEEVENTS_PERSISTED_FIELDS = {"mPackage", "mPackageToken",
+            "mClass", "mClassToken", "mTimeStamp", "mFlags", "mEventType", "mConfiguration",
+            "mShortcutId", "mShortcutIdToken", "mBucketAndReason", "mInstanceId",
+            "mNotificationChannelId", "mNotificationChannelIdToken", "mTaskRootPackage",
+            "mTaskRootPackageToken", "mTaskRootClass", "mTaskRootClassToken", "mLocusId",
+            "mLocusIdToken"};
+    // All fields in this list are defined in UsageEvents.Event but not persisted
+    private static final String[] USAGEEVENTS_IGNORED_FIELDS = {"mAction", "mContentAnnotations",
+            "mContentType", "DEVICE_EVENT_PACKAGE_NAME", "FLAG_IS_PACKAGE_INSTANT_APP",
+            "VALID_FLAG_BITS", "UNASSIGNED_TOKEN", "MAX_EVENT_TYPE"};
+    // All fields in this list are final constants defining event types and not persisted
+    private static final String[] EVENT_TYPES = {"NONE", "ACTIVITY_DESTROYED", "ACTIVITY_PAUSED",
+            "ACTIVITY_RESUMED", "ACTIVITY_STOPPED", "CHOOSER_ACTION", "CONFIGURATION_CHANGE",
+            "CONTINUE_PREVIOUS_DAY", "CONTINUING_FOREGROUND_SERVICE", "DEVICE_SHUTDOWN",
+            "DEVICE_STARTUP", "END_OF_DAY", "FLUSH_TO_DISK", "FOREGROUND_SERVICE_START",
+            "FOREGROUND_SERVICE_STOP", "KEYGUARD_HIDDEN", "KEYGUARD_SHOWN", "LOCUS_ID_SET",
+            "MOVE_TO_BACKGROUND", "MOVE_TO_FOREGROUND", "NOTIFICATION_INTERRUPTION",
+            "NOTIFICATION_SEEN", "ROLLOVER_FOREGROUND_SERVICE", "SCREEN_INTERACTIVE",
+            "SCREEN_NON_INTERACTIVE", "SHORTCUT_INVOCATION", "SLICE_PINNED", "SLICE_PINNED_PRIV",
+            "STANDBY_BUCKET_CHANGED", "SYSTEM_INTERACTION", "USER_INTERACTION", "USER_STOPPED",
+            "USER_UNLOCKED"};
+
+    @Test
+    public void testUsageEventsFields() {
+        final UsageEvents.Event event = new UsageEvents.Event();
+        final Field[] fields = event.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            final String name = field.getName();
+            if (!(ArrayUtils.contains(USAGEEVENTS_PERSISTED_FIELDS, name)
+                    || ArrayUtils.contains(USAGEEVENTS_IGNORED_FIELDS, name)
+                    || ArrayUtils.contains(EVENT_TYPES, name))) {
+                fail("Found an unknown field: " + name + ". Please correctly update either "
+                        + "USAGEEVENTS_PERSISTED_FIELDS or USAGEEVENTS_IGNORED_FIELDS. If this "
+                        + "field is a new event type, please update EVENT_TYPES instead.");
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4114b28..efcd458 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -259,4 +259,35 @@
         expectedConfig2.orientation = Configuration.ORIENTATION_LANDSCAPE;
         assertEquals(expectedConfig2, resources2.getConfiguration());
     }
+
+    @SmallTest
+    public void testOverrideDisplayAdjustments() {
+        final int originalOverrideDensity = 200;
+        final int overrideDisplayDensity = 400;
+        final Binder token = new Binder();
+        final Configuration overrideConfig = new Configuration();
+        overrideConfig.densityDpi = originalOverrideDensity;
+        final Resources resources = mResourcesManager.createBaseTokenResources(
+                token, APP_ONE_RES_DIR, null /* splitResDirs */, null /* overlayDirs */,
+                null /* libDirs */, Display.DEFAULT_DISPLAY, overrideConfig,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* classLoader */,
+                null /* loaders */);
+
+        // Update the override.
+        boolean handled = mResourcesManager.overrideTokenDisplayAdjustments(token,
+                adjustments -> adjustments.getConfiguration().densityDpi = overrideDisplayDensity);
+
+        assertTrue(handled);
+        assertTrue(resources.hasOverrideDisplayAdjustments());
+        assertEquals(overrideDisplayDensity,
+                resources.getDisplayAdjustments().getConfiguration().densityDpi);
+
+        // Clear the override.
+        handled = mResourcesManager.overrideTokenDisplayAdjustments(token, null /* override */);
+
+        assertTrue(handled);
+        assertFalse(resources.hasOverrideDisplayAdjustments());
+        assertEquals(originalOverrideDensity,
+                resources.getDisplayAdjustments().getConfiguration().densityDpi);
+    }
 }
diff --git a/core/tests/coretests/src/android/hardware/display/OWNERS b/core/tests/coretests/src/android/hardware/display/OWNERS
new file mode 100644
index 0000000..9ca3910
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/display/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+santoscordon@google.com
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
new file mode 100644
index 0000000..1a28b73
--- /dev/null
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -0,0 +1,9 @@
+# Display
+per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
+
+# Haptics
+per-file ExternalVibrationTest.java = michaelwr@google.com
+per-file VibrationEffectTest.java = michaelwr@google.com
+
+# Power
+per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
index afbf8db..2fc42e9 100644
--- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
+++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
@@ -19,6 +19,9 @@
 import static org.junit.Assert.assertEquals;
 
 import android.content.res.Configuration;
+import android.graphics.Point;
+import android.util.DisplayMetrics;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -67,4 +70,38 @@
 
         assertEquals(configuration, newAdjustments.getConfiguration());
     }
+
+    @Test
+    public void testFixedRotationAdjustments() {
+        final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
+        final int realRotation = Surface.ROTATION_0;
+        final int fixedRotation = Surface.ROTATION_90;
+
+        mDisplayAdjustments.setFixedRotationAdjustments(
+                new FixedRotationAdjustments(fixedRotation, null /* cutout */));
+
+        final int w = 1000;
+        final int h = 2000;
+        final Point size = new Point(w, h);
+        mDisplayAdjustments.adjustSize(size, realRotation);
+
+        assertEquals(fixedRotation, mDisplayAdjustments.getRotation(realRotation));
+        assertEquals(new Point(h, w), size);
+
+        final DisplayMetrics metrics = new DisplayMetrics();
+        metrics.xdpi = metrics.noncompatXdpi = w;
+        metrics.widthPixels = metrics.noncompatWidthPixels = w;
+        metrics.ydpi = metrics.noncompatYdpi = h;
+        metrics.heightPixels = metrics.noncompatHeightPixels = h;
+
+        final DisplayMetrics flippedMetrics = new DisplayMetrics();
+        flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = h;
+        flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h;
+        flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = w;
+        flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w;
+
+        mDisplayAdjustments.adjustMetrics(metrics, realRotation);
+
+        assertEquals(flippedMetrics, metrics);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
new file mode 100644
index 0000000..a3a3e7c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -0,0 +1,4 @@
+# Input
+per-file *MotionEventTest.* = michaelwr@google.com, svv@google.com
+per-file *KeyEventTest.* = michaelwr@google.com, svv@google.com
+per-file VelocityTest.java = michaelwr@google.com, svv@google.com
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index b21504c..c17c36e 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -93,6 +93,7 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityShortcutControllerTest {
     private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
+    private static final CharSequence PACKAGE_NAME_STRING = "Service name";
     private static final String SERVICE_NAME_SUMMARY = "Summary";
     private static final long VIBRATOR_PATTERN_1 = 100L;
     private static final long VIBRATOR_PATTERN_2 = 150L;
@@ -150,6 +151,8 @@
                 new AccessibilityManager(mHandler, mAccessibilityManagerService, 0);
         when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext))
                 .thenReturn(accessibilityManager);
+        when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE))
+                .thenReturn(accessibilityManager);
         when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
                 .thenReturn(mAlertDialogBuilder);
         when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
@@ -166,13 +169,13 @@
         ResolveInfo resolveInfo = mock(ResolveInfo.class);
         resolveInfo.serviceInfo = mock(ServiceInfo.class);
         resolveInfo.serviceInfo.applicationInfo = mApplicationInfo;
-        when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
+        when(resolveInfo.loadLabel(anyObject())).thenReturn(PACKAGE_NAME_STRING);
         when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
         when(mServiceInfo.getComponentName())
                 .thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
         when(mServiceInfo.loadSummary(any())).thenReturn(SERVICE_NAME_SUMMARY);
 
-        when(mAlertDialogBuilder.setTitle(anyInt())).thenReturn(mAlertDialogBuilder);
+        when(mAlertDialogBuilder.setTitle(anyObject())).thenReturn(mAlertDialogBuilder);
         when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
         when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
         when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
@@ -324,7 +327,8 @@
 
         assertEquals(1, Settings.Secure.getInt(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
-        verify(mResources).getString(R.string.accessibility_shortcut_toogle_warning);
+        verify(mResources).getString(
+                R.string.accessibility_shortcut_single_service_warning_title, PACKAGE_NAME_STRING);
         verify(mAlertDialog).show();
         verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
                 anyInt());
@@ -376,16 +380,20 @@
 
         ArgumentCaptor<DialogInterface.OnClickListener> captor =
                 ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
-        verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.disable_accessibility_shortcut),
+        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
                 captor.capture());
-        // Call the button callback
-        captor.getValue().onClick(null, 0);
+        // Call the button callback, if one exists
+        if (captor.getValue() != null) {
+            captor.getValue().onClick(null, 0);
+        }
         assertTrue(TextUtils.isEmpty(
                 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
+        assertEquals(0, Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
     }
 
     @Test
-    public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
+    public void testClickingTurnOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
@@ -393,8 +401,8 @@
 
         ArgumentCaptor<DialogInterface.OnClickListener> captor =
             ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
-        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.leave_accessibility_shortcut_on),
-            captor.capture());
+        verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+                captor.capture());
         // Call the button callback, if one exists
         if (captor.getValue() != null) {
             captor.getValue().onClick(null, 0);
@@ -402,7 +410,7 @@
         assertEquals(SERVICE_NAME_STRING,
                 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
         assertEquals(1, Settings.Secure.getInt(
-            mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
     }
 
     @Test
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 0390ac6..1cdc75a 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -225,7 +225,8 @@
                     CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* referrer */,
                     null /* voiceInteractor */, null /* state */, null /* persistentState */,
                     null /* pendingResults */, null /* pendingNewIntents */, true /* isForward */,
-                    null /* profilerInfo */,  mThread /* client */, null /* asssitToken */);
+                    null /* profilerInfo */,  mThread /* client */, null /* asssitToken */,
+                    null /* fixedRotationAdjustments */);
         }
 
         @Override
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index dfb7a16..1b1a624 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -87,6 +87,13 @@
 }
 
 prebuilt_etc {
+    name: "privapp_whitelist_com.android.car.secondaryhome",
+    sub_dir: "permissions",
+    src: "com.android.car.secondaryhome.xml",
+    filename_from_src: true,
+}
+
+prebuilt_etc {
     name: "privapp_whitelist_com.android.car.settings",
     sub_dir: "permissions",
     src: "com.android.car.settings.xml",
diff --git a/data/etc/car/com.android.car.secondaryhome.xml b/data/etc/car/com.android.car.secondaryhome.xml
index c74b86e..a8af906 100644
--- a/data/etc/car/com.android.car.secondaryhome.xml
+++ b/data/etc/car/com.android.car.secondaryhome.xml
@@ -20,5 +20,7 @@
         <permission name="android.permission.ACTIVITY_EMBEDDING"/>
         <!-- Required to send notification to current user-->
         <permission name="android.permission.MANAGE_USERS"/>
+        <!-- Required for CarNotificationLib -->
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
     </privapp-permissions>
 </permissions>
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 97b448a..c8f065a 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -70,9 +70,9 @@
  *  {@link Bitmap} objects.
  *
  *  <p>To use it, first create a {@link Source Source} using one of the
- *  {@code createSource} overloads. For example, to decode from a {@link File}, call
- *  {@link #createSource(File)} and pass the result to {@link #decodeDrawable(Source)}
- *  or {@link #decodeBitmap(Source)}:
+ *  {@code createSource} overloads. For example, to decode from a {@link Uri}, call
+ *  {@link #createSource(ContentResolver, Uri)} and pass the result to
+ *  {@link #decodeDrawable(Source)} or {@link #decodeBitmap(Source)}:
  *
  *  <pre class="prettyprint">
  *  File file = new File(...);
@@ -1032,7 +1032,11 @@
 
     /**
      * Create a new {@link Source Source} from a {@link java.io.File}.
-     *
+     * <p>
+     * This method should only be used for files that you have direct access to;
+     * if you'd like to work with files hosted outside your app, use an API like
+     * {@link #createSource(Callable)} or
+     * {@link #createSource(ContentResolver, Uri)}.
      * @return a new Source object, which can be passed to
      *      {@link #decodeDrawable decodeDrawable} or
      *      {@link #decodeBitmap decodeBitmap}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 7d14ef5..6179b48 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -98,7 +98,7 @@
 
     final Handler mHandler;
     @GuardedBy("sRouterLock")
-    private boolean mShouldUpdateRoutes;
+    private boolean mShouldUpdateRoutes = true;
     private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
     private volatile OnGetControllerHintsListener mOnGetControllerHintsListener;
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 0dc019c..4ebfce8 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -100,6 +100,7 @@
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         mPackageName = mContext.getPackageName();
         mHandler = new Handler(context.getMainLooper());
+        mHandler.post(this::getOrCreateClient);
     }
 
     /**
@@ -118,18 +119,6 @@
             Log.w(TAG, "Ignoring to add the same callback twice.");
             return;
         }
-
-        synchronized (sLock) {
-            if (mClient == null) {
-                Client client = new Client();
-                try {
-                    mMediaRouterService.registerManager(client, mPackageName);
-                    mClient = client;
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to register media router manager.", ex);
-                }
-            }
-        }
     }
 
     /**
@@ -144,21 +133,6 @@
             Log.w(TAG, "unregisterCallback: Ignore unknown callback. " + callback);
             return;
         }
-
-        synchronized (sLock) {
-            if (mCallbackRecords.size() == 0) {
-                if (mClient != null) {
-                    try {
-                        mMediaRouterService.unregisterManager(mClient);
-                    } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to unregister media router manager", ex);
-                    }
-                    mClient = null;
-                }
-                mRoutes.clear();
-                mPreferredFeaturesMap.clear();
-            }
-        }
     }
 
     /**
@@ -314,10 +288,7 @@
      */
     @NonNull
     public List<RoutingSessionInfo> getActiveSessions() {
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 return mMediaRouterService.getActiveSessions(client);
@@ -380,10 +351,7 @@
             return;
         }
 
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 int requestId = mNextRequestId.getAndIncrement();
@@ -419,10 +387,7 @@
             return;
         }
 
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 int requestId = mNextRequestId.getAndIncrement();
@@ -451,10 +416,7 @@
             return;
         }
 
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 int requestId = mNextRequestId.getAndIncrement();
@@ -710,15 +672,12 @@
             return;
         }
 
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 int requestId = mNextRequestId.getAndIncrement();
                 mMediaRouterService.selectRouteWithManager(
-                        mClient, requestId, sessionInfo.getId(), route);
+                        client, requestId, sessionInfo.getId(), route);
             } catch (RemoteException ex) {
                 Log.e(TAG, "selectRoute: Failed to send a request.", ex);
             }
@@ -755,15 +714,12 @@
             return;
         }
 
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 int requestId = mNextRequestId.getAndIncrement();
                 mMediaRouterService.deselectRouteWithManager(
-                        mClient, requestId, sessionInfo.getId(), route);
+                        client, requestId, sessionInfo.getId(), route);
             } catch (RemoteException ex) {
                 Log.e(TAG, "deselectRoute: Failed to send a request.", ex);
             }
@@ -794,14 +750,11 @@
         int requestId = mNextRequestId.getAndIncrement();
         mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route));
 
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 mMediaRouterService.transferToRouteWithManager(
-                        mClient, requestId, sessionInfo.getId(), route);
+                        client, requestId, sessionInfo.getId(), route);
             } catch (RemoteException ex) {
                 Log.e(TAG, "transferToRoute: Failed to send a request.", ex);
             }
@@ -821,15 +774,12 @@
     public void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
+        Client client = getOrCreateClient();
         if (client != null) {
             try {
                 int requestId = mNextRequestId.getAndIncrement();
                 mMediaRouterService.releaseSessionWithManager(
-                        mClient, requestId, sessionInfo.getId());
+                        client, requestId, sessionInfo.getId());
             } catch (RemoteException ex) {
                 Log.e(TAG, "releaseSession: Failed to send a request", ex);
             }
@@ -857,6 +807,23 @@
                 sessionInfo.getOwnerPackageName());
     }
 
+    private Client getOrCreateClient() {
+        synchronized (sLock) {
+            if (mClient != null) {
+                return mClient;
+            }
+            Client client = new Client();
+            try {
+                mMediaRouterService.registerManager(client, mPackageName);
+                mClient = client;
+                return client;
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to register media router manager.", ex);
+            }
+        }
+        return null;
+    }
+
     /**
      * Interface for receiving events about media routing changes.
      */
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index b5e2213..c4d27ec 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -23,8 +23,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.media.projection.IMediaProjection;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -86,6 +84,12 @@
      * capture request. Will be null if the result from the
      * startActivityForResult() is anything other than RESULT_OK.
      *
+     * Starting from Android {@link android.os.Build.VERSION_CODES#R}, if your application requests
+     * the {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission, and the
+     * user has not explicitly denied it, the permission will be automatically granted until the
+     * projection is stopped. This allows for user controls to be displayed on top of the screen
+     * being captured.
+     *
      * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int,
      * int, android.content.Intent)}
      * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int,
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index eee797a..c05c21c 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -603,6 +603,11 @@
         assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
+    @Test
+    public void testGetActiveSessions_returnsNonEmptyList() {
+        assertFalse(mManager.getActiveSessions().isEmpty());
+    }
+
     Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
             throws Exception {
         CountDownLatch addedLatch = new CountDownLatch(1);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 0c70e10..8f919c3 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -275,6 +275,13 @@
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
 
+    @Override
+    public Cursor queryChildDocumentsForManage(
+            String parentDocId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+        return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+    }
+
     /**
      * Check that the directory is the root of storage or blocked file from tree.
      *
diff --git a/packages/InputDevices/OWNERS b/packages/InputDevices/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/packages/InputDevices/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml b/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml
index b16d038..3660dc4 100644
--- a/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml
+++ b/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml
@@ -17,6 +17,7 @@
   -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_activated="true" android:color="@color/kg_user_switcher_activated_background_color" />
+    <item android:state_activated="true" android_state_enabled="true" android:color="@color/kg_user_switcher_activated_background_color" />
+    <item android:state_pressed="true" android:state_enabled="true" android:color="@color/kg_user_switcher_activated_background_color" />
     <item android:color="@android:color/transparent" />
-</selector>
\ No newline at end of file
+</selector>
diff --git a/packages/SystemUI/res/drawable/qs_media_background.xml b/packages/SystemUI/res/drawable/qs_media_background.xml
index e79c9a4..80db3be 100644
--- a/packages/SystemUI/res/drawable/qs_media_background.xml
+++ b/packages/SystemUI/res/drawable/qs_media_background.xml
@@ -19,4 +19,4 @@
     systemui:rippleMinSize="30dp"
     systemui:rippleMaxSize="135dp"
     systemui:highlight="15"
-    systemui:cornerRadius="@dimen/qs_media_corner_radius" />
\ No newline at end of file
+    systemui:cornerRadius="?android:attr/dialogCornerRadius" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index d89f329..da76c8d 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -40,7 +40,7 @@
         android:layout_height="wrap_content"
         style="@style/TextAppearance.AuthCredential.Description"/>
 
-    <EditText
+    <ImeAwareEditText
         android:id="@+id/lockPassword"
         android:layout_width="208dp"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_header.xml
index 20ec10c..a520719 100644
--- a/packages/SystemUI/res/layout/keyguard_media_header.xml
+++ b/packages/SystemUI/res/layout/keyguard_media_header.xml
@@ -45,109 +45,4 @@
         android:layout_height="match_parent"
     />
 
-    <!-- Layout for media controls. -->
-    <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/keyguard_media_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:gravity="center"
-        android:padding="16dp"
-    >
-        <ImageView
-            android:id="@+id/album_art"
-            android:layout_width="@dimen/qs_media_album_size"
-            android:layout_height="@dimen/qs_media_album_size"
-            android:layout_marginRight="16dp"
-            android:layout_weight="0"
-        />
-
-        <!-- Media information -->
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-        >
-            <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="center"
-            >
-                <com.android.internal.widget.CachingIconView
-                    android:id="@+id/icon"
-                    android:layout_width="16dp"
-                    android:layout_height="16dp"
-                    android:layout_marginEnd="5dp"
-                />
-                <TextView
-                    android:id="@+id/app_name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textSize="14sp"
-                    android:singleLine="true"
-                />
-            </LinearLayout>
-
-            <!-- Song name -->
-            <TextView
-                android:id="@+id/header_title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-                android:textSize="18sp"
-                android:paddingBottom="6dp"
-                android:gravity="center"/>
-
-            <!-- Artist name -->
-            <TextView
-                android:id="@+id/header_artist"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:fontFamily="@*android:string/config_bodyFontFamily"
-                android:textSize="14sp"
-                android:singleLine="true"
-            />
-        </LinearLayout>
-
-        <!-- Controls -->
-        <LinearLayout
-            android:id="@+id/media_actions"
-            android:orientation="horizontal"
-            android:layoutDirection="ltr"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            android:layout_gravity="center"
-        >
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action0"
-            />
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action1"
-            />
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action2"
-            />
-        </LinearLayout>
-    </LinearLayout>
-
 </com.android.systemui.statusbar.notification.stack.MediaHeaderView>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 149446c..03e7467 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -16,20 +16,22 @@
   -->
 
 <!-- Carousel for media controls -->
-<HorizontalScrollView
+<com.android.systemui.media.UnboundHorizontalScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:padding="@dimen/qs_media_padding"
     android:scrollbars="none"
-    android:visibility="gone"
+    android:clipChildren="false"
+    android:clipToPadding="false"
     >
     <LinearLayout
         android:id="@+id/media_carousel"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
+        android:clipChildren="false"
+        android:clipToPadding="false"
         >
         <!-- QSMediaPlayers will be added here dynamically -->
     </LinearLayout>
-</HorizontalScrollView>
+</com.android.systemui.media.UnboundHorizontalScrollView>
diff --git a/packages/SystemUI/res/layout/qqs_media_panel.xml b/packages/SystemUI/res/layout/qqs_media_panel.xml
deleted file mode 100644
index 2e86732..0000000
--- a/packages/SystemUI/res/layout/qqs_media_panel.xml
+++ /dev/null
@@ -1,90 +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
-  -->
-
-<!-- Layout for QQS media controls -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/qqs_media_controls"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:gravity="center"
-    android:paddingTop="16dp"
-    android:paddingLeft="16dp"
-    android:paddingRight="16dp"
-    android:paddingBottom="12dp"
-    android:background="@drawable/qs_media_background"
-    >
-    <!-- Top line: icon + song name -->
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:clipChildren="false"
-        android:gravity="center"
-        android:layout_marginBottom="12dp"
-        >
-        <com.android.internal.widget.CachingIconView
-            android:id="@+id/icon"
-            android:layout_width="14dp"
-            android:layout_height="14dp"
-            android:layout_marginEnd="5dp"
-        />
-        <TextView
-            android:id="@+id/header_title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:singleLine="true"
-        />
-    </LinearLayout>
-
-    <!-- Bottom section: controls -->
-    <LinearLayout
-        android:id="@+id/media_actions"
-        android:orientation="horizontal"
-        android:layoutDirection="ltr"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        >
-        <ImageButton
-            style="@style/MediaPlayer.Button"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:gravity="center"
-            android:visibility="gone"
-            android:id="@+id/action0"
-        />
-        <ImageButton
-            style="@style/MediaPlayer.Button"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:gravity="center"
-            android:visibility="gone"
-            android:id="@+id/action1"
-        />
-        <ImageButton
-            style="@style/MediaPlayer.Button"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:gravity="center"
-            android:visibility="gone"
-            android:id="@+id/action2"
-        />
-    </LinearLayout>
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 0c9ce39..ebfd0a0 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -23,7 +23,6 @@
     android:layout_height="@dimen/qs_footer_height"
     android:layout_marginStart="@dimen/qs_footer_margin"
     android:layout_marginEnd="@dimen/qs_footer_margin"
-    android:elevation="4dp"
     android:background="@android:color/transparent"
     android:baselineAligned="false"
     android:clickable="false"
@@ -128,13 +127,4 @@
             </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
         </com.android.keyguard.AlphaOptimizedLinearLayout>
     </LinearLayout>
-    <View
-        android:id="@+id/qs_drag_handle_view"
-        android:layout_width="48dp"
-        android:layout_height="4dp"
-        android:layout_marginTop="8dp"
-        android:layout_marginBottom="8dp"
-        android:layout_gravity="center_horizontal|bottom"
-        android:background="@drawable/qs_footer_drag_handle" />
-
 </com.android.systemui.qs.QSFooterImpl>
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
index d633ff4..9ad380d 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -16,236 +16,173 @@
   -->
 
 <!-- Layout for media controls inside QSPanel carousel -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/qs_media_controls"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:clipToPadding="false"
     android:gravity="center_horizontal|fill_vertical"
-    android:paddingTop="@dimen/qs_media_panel_outer_padding"
-    android:paddingBottom="@dimen/qs_media_panel_outer_padding"
-    android:background="@drawable/qs_media_background"
-    >
+    app:layoutDescription="@xml/media_scene">
+
+    <View
+        android:id="@+id/media_background"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/qs_media_background"
+        app:layout_constraintEnd_toEndOf="@id/view_width"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        />
+
+    <FrameLayout
+        android:id="@+id/notification_media_progress_time"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:forceHasOverlappingRendering="false">
+        <!-- width is set to "match_parent" to avoid extra layout calls -->
+        <TextView
+            android:id="@+id/media_elapsed_time"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:fontFamily="@*android:string/config_bodyFontFamily"
+            android:gravity="left"
+            android:textSize="14sp" />
+
+        <TextView
+            android:id="@+id/media_total_time"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:fontFamily="@*android:string/config_bodyFontFamily"
+            android:gravity="right"
+            android:textSize="14sp" />
+    </FrameLayout>
+
+    <ImageButton
+        android:id="@+id/action0"
+        style="@style/MediaPlayer.Button"
+        android:layout_width="48dp"
+        android:layout_height="48dp" />
+
+    <ImageButton
+        android:id="@+id/action1"
+        style="@style/MediaPlayer.Button"
+        android:layout_width="48dp"
+        android:layout_height="48dp" />
+
+    <ImageButton
+        android:id="@+id/action2"
+        style="@style/MediaPlayer.Button"
+        android:layout_width="52dp"
+        android:layout_height="52dp" />
+
+    <ImageButton
+        android:id="@+id/action3"
+        style="@style/MediaPlayer.Button"
+        android:layout_width="48dp"
+        android:layout_height="48dp" />
+
+    <ImageButton
+        android:id="@+id/action4"
+        style="@style/MediaPlayer.Button"
+        android:layout_width="48dp"
+        android:layout_height="48dp" />
+
+    <!-- Album Art -->
+    <ImageView
+        android:id="@+id/album_art"
+        android:layout_width="@dimen/qs_media_album_size"
+        android:layout_height="@dimen/qs_media_album_size" />
+
+    <!-- Seamless Output Switcher -->
+    <LinearLayout
+        android:id="@+id/media_seamless"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="@*android:drawable/media_seamless_background"
+        android:orientation="horizontal"
+        android:forceHasOverlappingRendering="false"
+        android:paddingLeft="12dp"
+        android:paddingTop="6dp"
+        android:paddingRight="12dp"
+        android:paddingBottom="6dp">
+
+        <ImageView
+            android:id="@+id/media_seamless_image"
+            android:layout_width="@dimen/qs_seamless_icon_size"
+            android:layout_height="@dimen/qs_seamless_icon_size"
+            android:layout_marginRight="8dp"
+            android:src="@*android:drawable/ic_media_seamless" />
+
+        <TextView
+            android:id="@+id/media_seamless_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_bodyFontFamily"
+            android:singleLine="true"
+            android:text="@*android:string/ext_media_seamless_action"
+            android:textSize="14sp" />
+    </LinearLayout>
+
+    <!-- Seek Bar -->
+    <SeekBar
+        android:id="@+id/media_progress_bar"
+        style="@android:style/Widget.ProgressBar.Horizontal"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:clickable="true"
+        android:maxHeight="3dp"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:splitTrack="false" />
+
+    <!-- App name -->
+    <TextView
+        android:id="@+id/app_name"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:textSize="14sp" />
+
+    <!-- Song name -->
+    <TextView
+        android:id="@+id/header_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:singleLine="true"
+        android:textSize="18sp" />
+
+    <!-- Artist name -->
+    <TextView
+        android:id="@+id/header_artist"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:singleLine="true"
+        android:textSize="14sp" />
+
+    <com.android.internal.widget.CachingIconView
+        android:id="@+id/icon"
+        android:layout_width="16dp"
+        android:layout_height="16dp" />
 
     <!-- Buttons to remove this view when no longer needed -->
     <include
         layout="@layout/qs_media_panel_options"
-        android:visibility="gone"/>
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="@id/view_width"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
 
-    <LinearLayout
-        android:id="@+id/media_guts"
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/view_width"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <!-- Header section -->
-        <LinearLayout
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
-            android:paddingStart="@dimen/qs_media_panel_outer_padding"
-            android:paddingEnd="16dp"
-        >
-
-            <ImageView
-                android:id="@+id/album_art"
-                android:layout_width="@dimen/qs_media_album_size"
-                android:layout_height="@dimen/qs_media_album_size"
-                android:layout_marginRight="16dp"
-                android:layout_weight="0"
-            />
-
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-            >
-                <LinearLayout
-                    android:orientation="horizontal"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:gravity="center"
-                >
-                    <com.android.internal.widget.CachingIconView
-                        android:id="@+id/icon"
-                        android:layout_width="16dp"
-                        android:layout_height="16dp"
-                        android:layout_marginEnd="5dp"
-                    />
-                    <TextView
-                        android:id="@+id/app_name"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:textSize="14sp"
-                        android:singleLine="true"
-                    />
-                </LinearLayout>
-
-                <!-- Song name -->
-                <TextView
-                    android:id="@+id/header_title"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:singleLine="true"
-                    android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-                    android:textSize="18sp"
-                    android:paddingBottom="6dp"
-                    android:gravity="center"/>
-
-                <!-- Artist name -->
-                <TextView
-                    android:id="@+id/header_artist"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:fontFamily="@*android:string/config_bodyFontFamily"
-                    android:textSize="14sp"
-                    android:singleLine="true"
-                />
-            </LinearLayout>
-
-            <!-- Output chip -->
-            <LinearLayout
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:visibility="gone"
-                android:paddingTop="6dp"
-                android:paddingBottom="6dp"
-                android:paddingLeft="12dp"
-                android:paddingRight="12dp"
-                android:gravity="center"
-                android:id="@+id/media_seamless"
-                android:background="@*android:drawable/media_seamless_background"
-                android:layout_weight="1"
-                android:forceHasOverlappingRendering="false"
-            >
-                <ImageView
-                    android:layout_width="@dimen/qs_seamless_icon_size"
-                    android:layout_height="@dimen/qs_seamless_icon_size"
-                    android:src="@*android:drawable/ic_media_seamless"
-                    android:layout_marginRight="8dp"
-                    android:id="@+id/media_seamless_image"
-                />
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:fontFamily="@*android:string/config_bodyFontFamily"
-                    android:text="@*android:string/ext_media_seamless_action"
-                    android:textSize="14sp"
-                    android:id="@+id/media_seamless_text"
-                    android:singleLine="true"
-                />
-            </LinearLayout>
-        </LinearLayout>
-
-        <!-- Seek Bar -->
-        <SeekBar
-            android:id="@+id/media_progress_bar"
-            style="@android:style/Widget.ProgressBar.Horizontal"
-            android:clickable="true"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:maxHeight="3dp"
-            android:paddingTop="24dp"
-            android:paddingBottom="24dp"
-            android:layout_marginBottom="-24dp"
-            android:layout_marginTop="-24dp"
-            android:splitTrack="false"
-        />
-
-        <FrameLayout
-            android:id="@+id/notification_media_progress_time"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingStart="@dimen/qs_media_panel_outer_padding"
-            android:paddingEnd="@dimen/qs_media_panel_outer_padding"
-            android:layout_marginBottom="10dp"
-            android:layout_gravity="center"
-            >
-            <!-- width is set to "match_parent" to avoid extra layout calls -->
-            <TextView
-                android:id="@+id/media_elapsed_time"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_alignParentLeft="true"
-                android:fontFamily="@*android:string/config_bodyFontFamily"
-                android:textSize="14sp"
-                android:gravity="left"
-            />
-            <TextView
-                android:id="@+id/media_total_time"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:fontFamily="@*android:string/config_bodyFontFamily"
-                android:layout_alignParentRight="true"
-                android:textSize="14sp"
-                android:gravity="right"
-            />
-        </FrameLayout>
-
-        <!-- Controls -->
-        <LinearLayout
-            android:id="@+id/media_actions"
-            android:orientation="horizontal"
-            android:layoutDirection="ltr"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingStart="@dimen/qs_media_panel_outer_padding"
-            android:paddingEnd="@dimen/qs_media_panel_outer_padding"
-            android:gravity="center"
-            >
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_marginStart="8dp"
-                android:layout_marginEnd="8dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action0"
-            />
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_marginStart="8dp"
-                android:layout_marginEnd="8dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action1"
-            />
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="52dp"
-                android:layout_height="52dp"
-                android:layout_marginStart="8dp"
-                android:layout_marginEnd="8dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action2"
-            />
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_marginStart="8dp"
-                android:layout_marginEnd="8dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action3"
-            />
-            <ImageButton
-                style="@style/MediaPlayer.Button"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_marginStart="8dp"
-                android:layout_marginEnd="8dp"
-                android:gravity="center"
-                android:visibility="gone"
-                android:id="@+id/action4"
-            />
-        </LinearLayout>
-    </LinearLayout>
-</LinearLayout>
+        app:layout_constraintGuide_begin="300dp" />
+</androidx.constraintlayout.motion.widget.MotionLayout>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 01dfeb2..cdf8426 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -54,20 +54,32 @@
         android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/qs_footer_height"
         android:elevation="4dp"
         android:background="@android:color/transparent"
         android:focusable="true"
-        android:accessibilityTraversalBefore="@android:id/edit"
-    />
+        android:accessibilityTraversalBefore="@android:id/edit">
+        <include layout="@layout/qs_footer_impl" />
+    </com.android.systemui.qs.QSPanel>
 
     <include layout="@layout/quick_status_bar_expanded_header" />
 
-    <include layout="@layout/qs_footer_impl" />
-
     <include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
 
     <include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
         android:visibility="gone" />
 
+    <FrameLayout
+        android:id="@+id/qs_drag_handle_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:elevation="4dp"
+        android:paddingBottom="5dp">
+        <View
+            android:layout_width="46dp"
+            android:layout_height="3dp"
+            android:background="@drawable/qs_footer_drag_handle" />
+    </FrameLayout>
+
+
 </com.android.systemui.qs.QSContainerImpl>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index e99b917..9a7c344 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -20,7 +20,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/header"
     android:layout_width="match_parent"
-    android:layout_height="@*android:dimen/quick_qs_total_height"
+    android:layout_height="wrap_content"
     android:layout_gravity="@integer/notification_panel_layout_gravity"
     android:background="@android:color/transparent"
     android:baselineAligned="false"
@@ -29,6 +29,7 @@
     android:clipToPadding="false"
     android:paddingTop="0dp"
     android:paddingEnd="0dp"
+    android:paddingBottom="10dp"
     android:paddingStart="0dp"
     android:elevation="4dp" >
 
@@ -45,8 +46,6 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/quick_qs_status_icons"
-        android:layout_marginStart="@dimen/qs_header_tile_margin_horizontal"
-        android:layout_marginEnd="@dimen/qs_header_tile_margin_horizontal"
         android:accessibilityTraversalAfter="@+id/date_time_group"
         android:accessibilityTraversalBefore="@id/expand_indicator"
         android:clipChildren="false"
@@ -54,15 +53,6 @@
         android:focusable="true"
         android:importantForAccessibility="yes" />
 
-    <com.android.systemui.statusbar.AlphaOptimizedImageView
-        android:id="@+id/qs_detail_header_progress"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:alpha="0"
-        android:background="@color/qs_detail_progress_track"
-        android:src="@drawable/indeterminate_anim"/>
-
     <TextView
         android:id="@+id/header_debug_info"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 5a8c5dc..d118d89 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -40,4 +40,6 @@
     <!-- Padding between status bar and bubbles when displayed in expanded state, smaller
          value in landscape since we have limited vertical space-->
     <dimen name="bubble_padding_top">4dp</dimen>
+
+    <dimen name="controls_activity_view_top_offset">25dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b677600..ef146f6 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -240,8 +240,8 @@
     <color name="magnification_border_color">#FF9900</color>
 
     <!-- controls -->
-    <color name="control_primary_text">@color/GM2_grey_100</color>
-    <color name="control_secondary_text">@color/GM2_grey_500</color>
+    <color name="control_primary_text">#E6FFFFFF</color>
+    <color name="control_secondary_text">#99FFFFFF</color>
     <color name="control_default_foreground">@color/GM2_grey_500</color>
     <color name="control_default_background">@color/GM2_grey_900</color>
     <color name="control_list_popup_background">@*android:color/background_floating_material_dark</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 221a64a..4984651 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -498,6 +498,7 @@
     <dimen name="qs_quick_tile_padding">12dp</dimen>
     <dimen name="qs_header_gear_translation">16dp</dimen>
     <dimen name="qs_header_tile_margin_horizontal">4dp</dimen>
+    <dimen name="qs_header_tile_margin_bottom">18dp</dimen>
     <dimen name="qs_page_indicator_width">16dp</dimen>
     <dimen name="qs_page_indicator_height">8dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
@@ -1043,6 +1044,10 @@
     <dimen name="bottom_padding">48dp</dimen>
     <dimen name="edge_margin">8dp</dimen>
 
+    <!-- The absolute side margins of quick settings -->
+    <dimen name="quick_settings_side_margins">16dp</dimen>
+    <dimen name="quick_settings_expanded_bottom_margin">16dp</dimen>
+    <dimen name="quick_settings_media_extra_bottom_margin">4dp</dimen>
     <dimen name="rounded_corner_content_padding">0dp</dimen>
     <dimen name="nav_content_padding">0dp</dimen>
     <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
@@ -1230,12 +1235,12 @@
 
     <!-- Size of media cards in the QSPanel carousel -->
     <dimen name="qs_media_width">350dp</dimen>
-    <dimen name="qs_media_padding">8dp</dimen>
+    <dimen name="qs_media_padding">16dp</dimen>
     <dimen name="qs_media_panel_outer_padding">16dp</dimen>
-    <dimen name="qs_media_corner_radius">10dp</dimen>
-    <dimen name="qs_media_album_size">72dp</dimen>
+    <dimen name="qs_media_album_size">52dp</dimen>
     <dimen name="qs_seamless_icon_size">20dp</dimen>
     <dimen name="qqs_media_spacing">8dp</dimen>
+    <dimen name="qqs_horizonal_tile_padding_bottom">8dp</dimen>
 
     <dimen name="magnification_border_size">5dp</dimen>
     <dimen name="magnification_frame_move_short">5dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8156e8d..76ca385 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -70,6 +70,26 @@
     <item type="id" name="panel_alpha_animator_end_tag"/>
     <item type="id" name="cross_fade_layer_type_changed_tag"/>
 
+    <item type="id" name="absolute_x_animator_tag"/>
+    <item type="id" name="absolute_x_animator_start_tag"/>
+    <item type="id" name="absolute_x_animator_end_tag"/>
+    <item type="id" name="absolute_x_current_value"/>
+
+    <item type="id" name="absolute_y_animator_tag"/>
+    <item type="id" name="absolute_y_animator_start_tag"/>
+    <item type="id" name="absolute_y_animator_end_tag"/>
+    <item type="id" name="absolute_y_current_value"/>
+
+    <item type="id" name="view_height_animator_tag"/>
+    <item type="id" name="view_height_animator_start_tag"/>
+    <item type="id" name="view_height_animator_end_tag"/>
+    <item type="id" name="view_height_current_value"/>
+
+    <item type="id" name="view_width_animator_tag"/>
+    <item type="id" name="view_width_animator_start_tag"/>
+    <item type="id" name="view_width_animator_end_tag"/>
+    <item type="id" name="view_width_current_value"/>
+
     <!-- Whether the icon is from a notification for which targetSdk < L -->
     <item type="id" name="icon_is_pre_L"/>
 
diff --git a/packages/SystemUI/res/xml/media_scene.xml b/packages/SystemUI/res/xml/media_scene.xml
new file mode 100644
index 0000000..f61b2b0
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_scene.xml
@@ -0,0 +1,447 @@
+<?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
+  -->
+<MotionScene
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <Transition
+        app:constraintSetStart="@id/collapsed"
+        app:constraintSetEnd="@id/expanded"
+        app:duration="1000" >
+        <KeyFrameSet >
+            <KeyPosition
+                app:motionTarget="@+id/action0"
+                app:keyPositionType="pathRelative"
+                app:framePosition="70"
+                app:sizePercent="0.9" />
+            <KeyPosition
+                app:motionTarget="@+id/action1"
+                app:keyPositionType="pathRelative"
+                app:framePosition="70"
+                app:sizePercent="0.9" />
+            <KeyPosition
+                app:motionTarget="@+id/action2"
+                app:keyPositionType="pathRelative"
+                app:framePosition="70"
+                app:sizePercent="0.9" />
+            <KeyPosition
+                app:motionTarget="@+id/action3"
+                app:keyPositionType="pathRelative"
+                app:framePosition="70"
+                app:sizePercent="0.9" />
+            <KeyPosition
+                app:motionTarget="@+id/action4"
+                app:keyPositionType="pathRelative"
+                app:framePosition="70"
+                app:sizePercent="0.9" />
+            <KeyPosition
+                app:motionTarget="@+id/media_progress_bar"
+                app:keyPositionType="pathRelative"
+                app:framePosition="70"
+                app:sizePercent="0.9" />
+            <KeyAttribute
+                app:motionTarget="@id/media_progress_bar"
+                app:framePosition="0"
+                android:alpha="0.0" />
+            <KeyAttribute
+                app:motionTarget="@+id/media_progress_bar"
+                app:framePosition="70"
+                android:alpha="0.0"/>
+            <KeyPosition
+                app:motionTarget="@+id/notification_media_progress_time"
+                app:keyPositionType="pathRelative"
+                app:framePosition="70"
+                app:sizePercent="0.9" />
+            <KeyAttribute
+                app:motionTarget="@id/notification_media_progress_time"
+                app:framePosition="0"
+                android:alpha="0.0" />
+            <KeyAttribute
+                app:motionTarget="@+id/notification_media_progress_time"
+                app:framePosition="70"
+                android:alpha="0.0"/>
+            <KeyAttribute
+                app:motionTarget="@id/action0"
+                app:framePosition="0"
+                android:alpha="0.0" />
+            <KeyAttribute
+                app:motionTarget="@+id/action0"
+                app:framePosition="70"
+                android:alpha="0.0"/>
+            <KeyAttribute
+                app:motionTarget="@id/action1"
+                app:framePosition="0"
+                android:alpha="0.0" />
+            <KeyAttribute
+                app:motionTarget="@+id/action1"
+                app:framePosition="70"
+                android:alpha="0.0"/>
+            <KeyAttribute
+                app:motionTarget="@id/action2"
+                app:framePosition="0"
+                android:alpha="0.0" />
+            <KeyAttribute
+                app:motionTarget="@+id/action2"
+                app:framePosition="70"
+                android:alpha="0.0"/>
+            <KeyAttribute
+                app:motionTarget="@id/action3"
+                app:framePosition="0"
+                android:alpha="0.0" />
+            <KeyAttribute
+                app:motionTarget="@+id/action3"
+                app:framePosition="70"
+                android:alpha="0.0"/>
+            <KeyAttribute
+                app:motionTarget="@id/action4"
+                app:framePosition="0"
+                android:alpha="0.0" />
+            <KeyAttribute
+                app:motionTarget="@+id/action4"
+                app:framePosition="70"
+                android:alpha="0.0"/>
+        </KeyFrameSet>
+    </Transition>
+
+    <ConstraintSet android:id="@+id/expanded">
+        <Constraint
+            android:id="@+id/icon"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginStart="18dp"
+            android:layout_marginTop="22dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            />
+
+        <Constraint
+            android:id="@+id/app_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="10dp"
+            android:layout_marginStart="10dp"
+            android:layout_marginTop="20dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintStart_toEndOf="@id/icon"
+            app:layout_constraintEnd_toStartOf="@id/media_seamless"
+            app:layout_constraintHorizontal_bias="0"
+            />
+
+        <Constraint
+            android:id="@+id/media_seamless"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_min="60dp"
+            android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+            />
+
+        <Constraint
+            android:id="@+id/album_art"
+            android:layout_width="@dimen/qs_media_album_size"
+            android:layout_height="@dimen/qs_media_album_size"
+            android:layout_marginTop="14dp"
+            android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintTop_toBottomOf="@+id/app_name"
+            app:layout_constraintStart_toStartOf="parent"
+            />
+
+        <!-- Song name -->
+        <Constraint
+            android:id="@+id/header_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginTop="17dp"
+            android:layout_marginStart="16dp"
+            app:layout_constraintTop_toBottomOf="@+id/app_name"
+            app:layout_constraintStart_toEndOf="@id/album_art"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            app:layout_constraintHorizontal_bias="0"/>
+
+        <!-- Artist name -->
+        <Constraint
+            android:id="@+id/header_artist"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginTop="3dp"
+            app:layout_constraintTop_toBottomOf="@id/header_title"
+            app:layout_constraintStart_toStartOf="@id/header_title"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            app:layout_constraintHorizontal_bias="0"/>
+
+        <!-- Seek Bar -->
+        <Constraint
+            android:id="@+id/media_progress_bar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="3dp"
+            app:layout_constraintTop_toBottomOf="@id/header_artist"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            />
+
+        <Constraint
+            android:id="@+id/notification_media_progress_time"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="38dp"
+            android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintTop_toBottomOf="@id/header_artist"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            />
+
+        <Constraint
+            android:id="@+id/action0"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginTop="5dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toLeftOf="@id/action1"
+            app:layout_constraintTop_toBottomOf="@id/notification_media_progress_time"
+            app:layout_constraintBottom_toBottomOf="parent">
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action1"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintLeft_toRightOf="@id/action0"
+            app:layout_constraintRight_toLeftOf="@id/action2"
+            app:layout_constraintTop_toTopOf="@id/action0"
+            app:layout_constraintBottom_toBottomOf="parent">
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action2"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintLeft_toRightOf="@id/action1"
+            app:layout_constraintRight_toLeftOf="@id/action3"
+            app:layout_constraintTop_toTopOf="@id/action0"
+            app:layout_constraintBottom_toBottomOf="parent">
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action3"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            app:layout_constraintLeft_toRightOf="@id/action2"
+            app:layout_constraintRight_toLeftOf="@id/action4"
+            app:layout_constraintTop_toTopOf="@id/action0"
+            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintBottom_toBottomOf="parent">
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action4"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintLeft_toRightOf="@id/action3"
+            app:layout_constraintRight_toRightOf="@id/view_width"
+            app:layout_constraintTop_toTopOf="@id/action0"
+            app:layout_constraintBottom_toBottomOf="parent">
+        </Constraint>
+    </ConstraintSet>
+
+    <ConstraintSet android:id="@+id/collapsed">
+        <Constraint
+            android:id="@+id/icon"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginStart="18dp"
+            android:layout_marginTop="22dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            />
+
+        <Constraint
+            android:id="@+id/app_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="10dp"
+            android:layout_marginStart="10dp"
+            android:layout_marginTop="20dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintStart_toEndOf="@id/icon"
+            app:layout_constraintEnd_toStartOf="@id/media_seamless"
+            app:layout_constraintHorizontal_bias="0"
+            />
+
+        <Constraint
+            android:id="@+id/media_seamless"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_min="60dp"
+            android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+            />
+
+        <Constraint
+            android:id="@+id/album_art"
+            android:layout_width="@dimen/qs_media_album_size"
+            android:layout_height="@dimen/qs_media_album_size"
+            android:layout_marginTop="16dp"
+            android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginBottom="24dp"
+            app:layout_constraintTop_toBottomOf="@id/icon"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            />
+
+        <!-- Song name -->
+        <Constraint
+            android:id="@+id/header_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="17dp"
+            android:layout_marginStart="16dp"
+            app:layout_constraintTop_toBottomOf="@id/app_name"
+            app:layout_constraintBottom_toTopOf="@id/header_artist"
+            app:layout_constraintStart_toEndOf="@id/album_art"
+            app:layout_constraintEnd_toStartOf="@id/action0"
+            app:layout_constraintHorizontal_bias="0"/>
+
+        <!-- Artist name -->
+        <Constraint
+            android:id="@+id/header_artist"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="3dp"
+            android:layout_marginBottom="24dp"
+            app:layout_constraintTop_toBottomOf="@id/header_title"
+            app:layout_constraintStart_toStartOf="@id/header_title"
+            app:layout_constraintEnd_toStartOf="@id/action0"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="0"/>
+
+        <!-- Seek Bar -->
+        <Constraint
+            android:id="@+id/media_progress_bar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:alpha="0.0"
+            app:layout_constraintTop_toBottomOf="@id/album_art"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            android:visibility="gone"
+            />
+
+        <Constraint
+            android:id="@+id/notification_media_progress_time"
+            android:alpha="0.0"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="35dp"
+            android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+            app:layout_constraintTop_toBottomOf="@id/album_art"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/view_width"
+            android:visibility="gone"
+            />
+
+        <Constraint
+            android:id="@+id/action0"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginTop="16dp"
+            android:visibility="gone"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintTop_toBottomOf="@id/app_name"
+            app:layout_constraintLeft_toRightOf="@id/header_title"
+            app:layout_constraintRight_toLeftOf="@id/action1"
+            >
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action1"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginTop="18dp"
+            app:layout_constraintTop_toBottomOf="@id/app_name"
+            app:layout_constraintLeft_toRightOf="@id/action0"
+            app:layout_constraintRight_toLeftOf="@id/action2"
+            >
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action2"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginTop="18dp"
+            app:layout_constraintTop_toBottomOf="@id/app_name"
+            app:layout_constraintLeft_toRightOf="@id/action1"
+            app:layout_constraintRight_toLeftOf="@id/action3"
+            >
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action3"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginTop="18dp"
+            app:layout_constraintTop_toBottomOf="@id/app_name"
+            app:layout_constraintLeft_toRightOf="@id/action2"
+            app:layout_constraintRight_toLeftOf="@id/action4"
+            >
+        </Constraint>
+
+        <Constraint
+            android:id="@+id/action4"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
+            android:layout_marginEnd="4dp"
+            android:visibility="gone"
+            android:layout_marginTop="18dp"
+            app:layout_constraintTop_toBottomOf="@id/app_name"
+            app:layout_constraintLeft_toRightOf="@id/action3"
+            app:layout_constraintRight_toRightOf="@id/view_width"
+            >
+        </Constraint>
+    </ConstraintSet>
+</MotionScene>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt
deleted file mode 100644
index 487c295..0000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.graphics.drawable.Drawable
-
-import java.util.List
-
-/** State for lock screen media controls. */
-data class KeyguardMedia(
-    val foregroundColor: Int,
-    val backgroundColor: Int,
-    val app: String?,
-    val appIcon: Drawable?,
-    val artist: String?,
-    val song: String?,
-    val artwork: Drawable?,
-    val actionIcons: List<Drawable>
-)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
deleted file mode 100644
index af5196f..0000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
-import androidx.palette.graphics.Palette;
-
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaControllerFactory;
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Media controls to display on the lockscreen
- *
- * TODO: Should extend MediaControlPanel to avoid code duplication.
- * Unfortunately, it isn't currently possible because the ActivatableNotificationView background is
- * different.
- */
-@Singleton
-public class KeyguardMediaPlayer {
-
-    private static final String TAG = "KeyguardMediaPlayer";
-    // Buttons that can be displayed on lock screen media controls.
-    private static final int[] ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
-
-    private final Context mContext;
-    private final Executor mBackgroundExecutor;
-    private final KeyguardMediaViewModel mViewModel;
-    private KeyguardMediaObserver mObserver;
-
-    @Inject
-    public KeyguardMediaPlayer(Context context, MediaControllerFactory factory,
-            @Background Executor backgroundExecutor) {
-        mContext = context;
-        mBackgroundExecutor = backgroundExecutor;
-        mViewModel = new KeyguardMediaViewModel(context, factory);
-    }
-
-    /** Binds media controls to a view hierarchy. */
-    public void bindView(View v) {
-        if (mObserver != null) {
-            throw new IllegalStateException("cannot bind views, already bound");
-        }
-        mViewModel.loadDimens();
-        mObserver = new KeyguardMediaObserver(v);
-        // Control buttons
-        for (int i = 0; i < ACTION_IDS.length; i++) {
-            ImageButton button = v.findViewById(ACTION_IDS[i]);
-            if (button == null) {
-                continue;
-            }
-            final int index = i;
-            button.setOnClickListener(unused -> mViewModel.onActionClick(index));
-        }
-        mViewModel.getKeyguardMedia().observeForever(mObserver);
-    }
-
-    /** Unbinds media controls. */
-    public void unbindView() {
-        if (mObserver == null) {
-            throw new IllegalStateException("cannot unbind views, nothing bound");
-        }
-        mViewModel.getKeyguardMedia().removeObserver(mObserver);
-        mObserver = null;
-    }
-
-    /** Clear the media controls because there isn't an active session. */
-    public void clearControls() {
-        mBackgroundExecutor.execute(mViewModel::clearControls);
-    }
-
-    /**
-     * Update the media player
-     *
-     * TODO: consider registering a MediaLister instead of exposing this update method.
-     *
-     * @param entry Media notification that will be used to update the player
-     * @param appIcon Icon for the app playing the media
-     * @param mediaMetadata Media metadata that will be used to update the player
-     */
-    public void updateControls(NotificationEntry entry, Icon appIcon,
-            MediaMetadata mediaMetadata) {
-        if (mObserver == null) {
-            throw new IllegalStateException("cannot update controls, views not bound");
-        }
-        if (mediaMetadata == null) {
-            Log.d(TAG, "media metadata was null, closing media controls");
-            // Note that clearControls() executes on the same background executor, so there
-            // shouldn't be an issue with an outdated update running after clear. However, if stale
-            // controls are observed then consider removing any enqueued updates.
-            clearControls();
-            return;
-        }
-        mBackgroundExecutor.execute(() -> mViewModel.updateControls(entry, appIcon, mediaMetadata));
-    }
-
-    /** ViewModel for KeyguardMediaControls. */
-    private static final class KeyguardMediaViewModel {
-
-        private final Context mContext;
-        private final MediaControllerFactory mMediaControllerFactory;
-        private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>();
-        private final Object mActionsLock = new Object();
-        private List<PendingIntent> mActions;
-        private float mAlbumArtRadius;
-        private int mAlbumArtSize;
-
-        KeyguardMediaViewModel(Context context, MediaControllerFactory factory) {
-            mContext = context;
-            mMediaControllerFactory = factory;
-            loadDimens();
-        }
-
-        /** Close the media player because there isn't an active session. */
-        public void clearControls() {
-            synchronized (mActionsLock) {
-                mActions = null;
-            }
-            mMedia.postValue(null);
-        }
-
-        /** Update the media player with information about the active session. */
-        public void updateControls(NotificationEntry entry, Icon appIcon,
-                MediaMetadata mediaMetadata) {
-
-            // Check the playback state of the media controller. If it is null, then the session was
-            // probably destroyed. Don't update in this case.
-            final MediaSession.Token token = entry.getSbn().getNotification().extras
-                    .getParcelable(Notification.EXTRA_MEDIA_SESSION);
-            final MediaController controller = token != null
-                    ? mMediaControllerFactory.create(token) : null;
-            if (controller != null && controller.getPlaybackState() == null) {
-                clearControls();
-                return;
-            }
-
-            // Foreground and Background colors computed from album art
-            Notification notif = entry.getSbn().getNotification();
-            int fgColor = notif.color;
-            int bgColor = entry.getRow() == null ? -1 : entry.getRow().getCurrentBackgroundTint();
-            Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
-            if (artworkBitmap == null) {
-                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-            }
-            if (artworkBitmap != null) {
-                // If we have art, get colors from that
-                Palette p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
-                        .generate();
-                Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(p);
-                bgColor = swatch.getRgb();
-                fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p);
-            }
-            // Make sure colors will be legible
-            boolean isDark = !ContrastColorUtil.isColorLight(bgColor);
-            fgColor = ContrastColorUtil.resolveContrastColor(mContext, fgColor, bgColor,
-                    isDark);
-            fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark);
-
-            // Album art
-            RoundedBitmapDrawable artwork = null;
-            if (artworkBitmap != null) {
-                Bitmap original = artworkBitmap.copy(Bitmap.Config.ARGB_8888, true);
-                Bitmap scaled = Bitmap.createScaledBitmap(original, mAlbumArtSize, mAlbumArtSize,
-                        false);
-                artwork = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
-                artwork.setCornerRadius(mAlbumArtRadius);
-            }
-
-            // App name
-            Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
-            String app = builder.loadHeaderAppName();
-
-            // App Icon
-            Drawable appIconDrawable = appIcon.loadDrawable(mContext);
-
-            // Song name
-            String song = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
-
-            // Artist name
-            String artist = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
-
-            // Control buttons
-            List<Drawable> actionIcons = new ArrayList<>();
-            final List<PendingIntent> intents = new ArrayList<>();
-            Notification.Action[] actions = notif.actions;
-            final int[] actionsToShow = notif.extras.getIntArray(
-                    Notification.EXTRA_COMPACT_ACTIONS);
-
-            Context packageContext = entry.getSbn().getPackageContext(mContext);
-            for (int i = 0; i < ACTION_IDS.length; i++) {
-                if (actionsToShow != null && actions != null && i < actionsToShow.length
-                        && actionsToShow[i] < actions.length) {
-                    final int idx = actionsToShow[i];
-                    actionIcons.add(actions[idx].getIcon().loadDrawable(packageContext));
-                    intents.add(actions[idx].actionIntent);
-                } else {
-                    actionIcons.add(null);
-                    intents.add(null);
-                }
-            }
-            synchronized (mActionsLock) {
-                mActions = intents;
-            }
-
-            KeyguardMedia data = new KeyguardMedia(fgColor, bgColor, app, appIconDrawable, artist,
-                    song, artwork, actionIcons);
-            mMedia.postValue(data);
-        }
-
-        /** Gets state for the lock screen media controls. */
-        public LiveData<KeyguardMedia> getKeyguardMedia() {
-            return mMedia;
-        }
-
-        /**
-         * Handle user clicks on media control buttons (actions).
-         *
-         * @param index position of the button that was clicked.
-         */
-        public void onActionClick(int index) {
-            PendingIntent intent = null;
-            // This might block the ui thread to wait for the lock. Currently, however, the
-            // lock is held by the bg thread to assign a member, which should be fast. An
-            // alternative could be to add the intents to the state and let the observer set
-            // the onClick listeners.
-            synchronized (mActionsLock) {
-                if (mActions != null && index < mActions.size()) {
-                    intent = mActions.get(index);
-                }
-            }
-            if (intent != null) {
-                try {
-                    intent.send();
-                } catch (PendingIntent.CanceledException e) {
-                    Log.d(TAG, "failed to send action intent", e);
-                }
-            }
-        }
-
-        void loadDimens() {
-            mAlbumArtRadius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
-            mAlbumArtSize = (int) mContext.getResources().getDimension(
-                    R.dimen.qs_media_album_size);
-        }
-    }
-
-    /** Observer for state changes of lock screen media controls. */
-    private static final class KeyguardMediaObserver implements Observer<KeyguardMedia> {
-
-        private final View mRootView;
-        private final MediaHeaderView mMediaHeaderView;
-        private final ImageView mAlbumView;
-        private final ImageView mAppIconView;
-        private final TextView mAppNameView;
-        private final TextView mTitleView;
-        private final TextView mArtistView;
-        private final List<ImageButton> mButtonViews = new ArrayList<>();
-
-        KeyguardMediaObserver(View v) {
-            mRootView = v;
-            mMediaHeaderView = v instanceof MediaHeaderView ? (MediaHeaderView) v : null;
-            mAlbumView = v.findViewById(R.id.album_art);
-            mAppIconView = v.findViewById(R.id.icon);
-            mAppNameView = v.findViewById(R.id.app_name);
-            mTitleView = v.findViewById(R.id.header_title);
-            mArtistView = v.findViewById(R.id.header_artist);
-            for (int i = 0; i < ACTION_IDS.length; i++) {
-                mButtonViews.add(v.findViewById(ACTION_IDS[i]));
-            }
-        }
-
-        /** Updates lock screen media player views when state changes. */
-        @Override
-        public void onChanged(KeyguardMedia data) {
-            if (data == null) {
-                mRootView.setVisibility(View.GONE);
-                return;
-            }
-            mRootView.setVisibility(View.VISIBLE);
-
-            // Background color
-            if (mMediaHeaderView != null) {
-                mMediaHeaderView.setBackgroundColor(data.getBackgroundColor());
-            }
-
-            // Album art
-            if (mAlbumView != null) {
-                mAlbumView.setImageDrawable(data.getArtwork());
-                mAlbumView.setVisibility(data.getArtwork() == null ? View.GONE : View.VISIBLE);
-            }
-
-            // App icon
-            if (mAppIconView != null) {
-                Drawable iconDrawable = data.getAppIcon();
-                iconDrawable.setTint(data.getForegroundColor());
-                mAppIconView.setImageDrawable(iconDrawable);
-            }
-
-            // App name
-            if (mAppNameView != null) {
-                String appNameString = data.getApp();
-                mAppNameView.setText(appNameString);
-                mAppNameView.setTextColor(data.getForegroundColor());
-            }
-
-            // Song name
-            if (mTitleView != null) {
-                mTitleView.setText(data.getSong());
-                mTitleView.setTextColor(data.getForegroundColor());
-            }
-
-            // Artist name
-            if (mArtistView != null) {
-                mArtistView.setText(data.getArtist());
-                mArtistView.setTextColor(data.getForegroundColor());
-            }
-
-            // Control buttons
-            for (int i = 0; i < ACTION_IDS.length; i++) {
-                ImageButton button = mButtonViews.get(i);
-                if (button == null) {
-                    continue;
-                }
-                Drawable icon = data.getActionIcons().get(i);
-                if (icon == null) {
-                    button.setVisibility(View.GONE);
-                    button.setImageDrawable(null);
-                } else {
-                    button.setVisibility(View.VISIBLE);
-                    button.setImageDrawable(icon);
-                    button.setImageTintList(ColorStateList.valueOf(data.getForegroundColor()));
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 58a233d..50c4c51 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -33,6 +33,7 @@
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
@@ -248,8 +249,7 @@
     // Battery status
     private BatteryStatus mBatteryStatus;
 
-    @VisibleForTesting
-    protected StrongAuthTracker mStrongAuthTracker;
+    private StrongAuthTracker mStrongAuthTracker;
 
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
@@ -1513,6 +1513,16 @@
         mUserTrustIsUsuallyManaged.delete(userId);
     }
 
+    @VisibleForTesting
+    protected void setStrongAuthTracker(@NonNull StrongAuthTracker tracker) {
+        if (mStrongAuthTracker != null) {
+            mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
+        }
+
+        mStrongAuthTracker = tracker;
+        mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+    }
+
     private void registerRingerTracker() {
         mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver);
     }
@@ -1526,7 +1536,8 @@
             DumpManager dumpManager,
             RingerModeTracker ringerModeTracker,
             @Background Executor backgroundExecutor,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            LockPatternUtils lockPatternUtils) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
@@ -1535,6 +1546,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mRingerModeTracker = ringerModeTracker;
         mStatusBarStateController = statusBarStateController;
+        mLockPatternUtils = lockPatternUtils;
         dumpManager.registerDumpable(getClass().getName(), this);
 
         mHandler = new Handler(mainLooper) {
@@ -1703,8 +1715,8 @@
 
         mTrustManager = context.getSystemService(TrustManager.class);
         mTrustManager.registerTrustListener(this);
-        mLockPatternUtils = new LockPatternUtils(context);
-        mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+
+        setStrongAuthTracker(mStrongAuthTracker);
 
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.getService(DreamService.DREAM_SERVICE));
@@ -2910,6 +2922,9 @@
         mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
         mRingerModeTracker.getRingerMode().removeObserver(mRingerModeObserver);
 
+        mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
+        mTrustManager.unregisterTrustListener(this);
+
         mHandler.removeCallbacksAndMessages(null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index b727563..0135e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -128,6 +128,8 @@
     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT =
             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13
 
+    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
     private Recents mRecents;
     private StatusBar mStatusBar;
     private SystemActionsBroadcastReceiver mReceiver;
@@ -147,7 +149,11 @@
 
     @Override
     public void start() {
-        mContext.registerReceiverForAllUsers(mReceiver, mReceiver.createIntentFilter(), null, null);
+        mContext.registerReceiverForAllUsers(
+                mReceiver,
+                mReceiver.createIntentFilter(),
+                PERMISSION_SELF,
+                null);
         registerActions();
     }
 
@@ -397,6 +403,7 @@
                 case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
                 case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
                     Intent intent = new Intent(intentAction);
+                    intent.setPackage(context.getPackageName());
                     return PendingIntent.getBroadcast(context, 0, intent, 0);
                 }
                 default:
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index ca0cd77..6a5e94c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -175,7 +175,7 @@
     private INotificationManager mINotificationManager;
 
     // Callback that updates BubbleOverflowActivity on data change.
-    @Nullable private BubbleData.Listener mOverflowListener = null;
+    @Nullable private Runnable mOverflowCallback = null;
 
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private IStatusBarService mBarService;
@@ -280,31 +280,6 @@
         }
     }
 
-    public BubbleController(Context context,
-            NotificationShadeWindowController notificationShadeWindowController,
-            StatusBarStateController statusBarStateController,
-            ShadeController shadeController,
-            BubbleData data,
-            ConfigurationController configurationController,
-            NotificationInterruptStateProvider interruptionStateProvider,
-            ZenModeController zenModeController,
-            NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManager groupManager,
-            NotificationEntryManager entryManager,
-            NotifPipeline notifPipeline,
-            FeatureFlags featureFlags,
-            DumpManager dumpManager,
-            FloatingContentCoordinator floatingContentCoordinator,
-            BubbleDataRepository dataRepository,
-            SysUiState sysUiState,
-            INotificationManager notificationManager) {
-        this(context, notificationShadeWindowController, statusBarStateController, shadeController,
-                data, null /* synchronizer */, configurationController, interruptionStateProvider,
-                zenModeController, notifUserManager, groupManager, entryManager,
-                notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
-                dataRepository, sysUiState, notificationManager);
-    }
-
     /**
      * Injected constructor. See {@link BubbleModule}.
      */
@@ -326,7 +301,8 @@
             FloatingContentCoordinator floatingContentCoordinator,
             BubbleDataRepository dataRepository,
             SysUiState sysUiState,
-            INotificationManager notificationManager) {
+            INotificationManager notificationManager,
+            WindowManager windowManager) {
         dumpManager.registerDumpable(TAG, this);
         mContext = context;
         mShadeController = shadeController;
@@ -395,7 +371,7 @@
         }
         mSurfaceSynchronizer = synchronizer;
 
-        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        mWindowManager = windowManager;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
@@ -595,8 +571,8 @@
         mInflateSynchronously = inflateSynchronously;
     }
 
-    void setOverflowListener(BubbleData.Listener listener) {
-        mOverflowListener = listener;
+    void setOverflowCallback(Runnable updateOverflow) {
+        mOverflowCallback = updateOverflow;
     }
 
     /**
@@ -1010,8 +986,8 @@
         @Override
         public void applyUpdate(BubbleData.Update update) {
             // Update bubbles in overflow.
-            if (mOverflowListener != null) {
-                mOverflowListener.applyUpdate(update);
+            if (mOverflowCallback != null) {
+                mOverflowCallback.run();
             }
 
             // Collapsing? Do this first before remaining steps.
@@ -1039,8 +1015,7 @@
                     if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
                         && (!bubble.showInShade()
                             || reason == DISMISS_NOTIF_CANCEL
-                            || reason == DISMISS_GROUP_CANCELLED
-                            || reason == DISMISS_OVERFLOW_MAX_REACHED)) {
+                            || reason == DISMISS_GROUP_CANCELLED)) {
                         // The bubble is now gone & the notification is hidden from the shade, so
                         // time to actually remove it
                         for (NotifCallback cb : mCallbacks) {
@@ -1111,6 +1086,9 @@
                     Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
                             mStackView.getExpandedBubble()));
                 }
+                Log.d(TAG, "\n[BubbleData] overflow:");
+                Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
+                        null) + "\n");
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index f2b1c03..65d5beb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -74,8 +74,6 @@
         @Nullable Bubble selectedBubble;
         @Nullable Bubble addedBubble;
         @Nullable Bubble updatedBubble;
-        @Nullable Bubble addedOverflowBubble;
-        @Nullable Bubble removedOverflowBubble;
         // Pair with Bubble and @DismissReason Integer
         final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>();
 
@@ -94,12 +92,10 @@
                     || addedBubble != null
                     || updatedBubble != null
                     || !removedBubbles.isEmpty()
-                    || addedOverflowBubble != null
-                    || removedOverflowBubble != null
                     || orderChanged;
         }
 
-        void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
+        void bubbleRemoved(Bubble bubbleToRemove, @DismissReason  int reason) {
             removedBubbles.add(new Pair<>(bubbleToRemove, reason));
         }
     }
@@ -237,7 +233,6 @@
 
     private void moveOverflowBubbleToPending(Bubble b) {
         mOverflowBubbles.remove(b);
-        mStateChange.removedOverflowBubble = b;
         mPendingBubbles.add(b);
     }
 
@@ -445,9 +440,8 @@
                 if (DEBUG_BUBBLE_DATA) {
                     Log.d(TAG, "Cancel overflow bubble: " + b);
                 }
-                mOverflowBubbles.remove(b);
                 mStateChange.bubbleRemoved(b, reason);
-                mStateChange.removedOverflowBubble = b;
+                mOverflowBubbles.remove(b);
             }
             return;
         }
@@ -489,7 +483,6 @@
             Log.d(TAG, "Overflowing: " + bubble);
         }
         mOverflowBubbles.add(0, bubble);
-        mStateChange.addedOverflowBubble = bubble;
         bubble.stopInflation();
         if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
             // Remove oldest bubble.
@@ -497,9 +490,8 @@
             if (DEBUG_BUBBLE_DATA) {
                 Log.d(TAG, "Overflow full. Remove: " + oldest);
             }
-            mOverflowBubbles.remove(oldest);
-            mStateChange.removedOverflowBubble = oldest;
             mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED);
+            mOverflowBubbles.remove(oldest);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index c2ca9fa..0074249 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -108,12 +108,11 @@
         mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
                 mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
         mRecyclerView.setAdapter(mAdapter);
-
-        mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles());
-        mAdapter.notifyDataSetChanged();
-        setEmptyStateVisibility();
-
-        mBubbleController.setOverflowListener(mDataListener);
+        onDataChanged(mBubbleController.getOverflowBubbles());
+        mBubbleController.setOverflowCallback(() -> {
+            onDataChanged(mBubbleController.getOverflowBubbles());
+        });
+        onThemeChanged();
     }
 
     /**
@@ -140,14 +139,6 @@
         }
     }
 
-    void setEmptyStateVisibility() {
-        if (mOverflowBubbles.isEmpty()) {
-            mEmptyState.setVisibility(View.VISIBLE);
-        } else {
-            mEmptyState.setVisibility(View.GONE);
-        }
-    }
-
     void setBackgroundColor() {
         final TypedArray ta = getApplicationContext().obtainStyledAttributes(
                 new int[]{android.R.attr.colorBackgroundFloating});
@@ -156,40 +147,22 @@
         findViewById(android.R.id.content).setBackgroundColor(bgColor);
     }
 
-    private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
+    void onDataChanged(List<Bubble> bubbles) {
+        mOverflowBubbles.clear();
+        mOverflowBubbles.addAll(bubbles);
+        mAdapter.notifyDataSetChanged();
 
-        @Override
-        public void applyUpdate(BubbleData.Update update) {
-
-            Bubble toRemove = update.removedOverflowBubble;
-            if (toRemove != null) {
-                if (DEBUG_OVERFLOW) {
-                    Log.d(TAG, "remove: " + toRemove);
-                }
-                toRemove.cleanupViews();
-                int i = mOverflowBubbles.indexOf(toRemove);
-                mOverflowBubbles.remove(toRemove);
-                mAdapter.notifyItemRemoved(i);
-            }
-
-            Bubble toAdd = update.addedOverflowBubble;
-            if (toAdd != null) {
-                if (DEBUG_OVERFLOW) {
-                    Log.d(TAG, "add: " + toAdd);
-                }
-                mOverflowBubbles.add(0, toAdd);
-                mAdapter.notifyItemInserted(0);
-            }
-
-            setEmptyStateVisibility();
-
-            if (DEBUG_OVERFLOW) {
-                Log.d(TAG, BubbleDebugConfig.formatBubblesString(
-                        mBubbleController.getOverflowBubbles(),
-                        null));
-            }
+        if (mOverflowBubbles.isEmpty()) {
+            mEmptyState.setVisibility(View.VISIBLE);
+        } else {
+            mEmptyState.setVisibility(View.GONE);
         }
-    };
+
+        if (DEBUG_OVERFLOW) {
+            Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
+                    mOverflowBubbles, /*selected*/ null));
+        }
+    }
 
     @Override
     public void onStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index e3b630b..d1d07f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -18,6 +18,7 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
+import android.view.WindowManager;
 
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubbleData;
@@ -68,7 +69,8 @@
             FloatingContentCoordinator floatingContentCoordinator,
             BubbleDataRepository bubbleDataRepository,
             SysUiState sysUiState,
-            INotificationManager notifManager) {
+            INotificationManager notifManager,
+            WindowManager windowManager) {
         return new BubbleController(
                 context,
                 notificationShadeWindowController,
@@ -88,6 +90,7 @@
                 floatingContentCoordinator,
                 bubbleDataRepository,
                 sysUiState,
-                notifManager);
+                notifManager,
+                windowManager);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index 1f07e37..b6c09f1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -50,7 +50,8 @@
     fun createPinDialog(
         cvh: ControlViewHolder,
         useAlphaNumeric: Boolean,
-        useRetryStrings: Boolean
+        useRetryStrings: Boolean,
+        onCancel: () -> Unit
     ): Dialog? {
         val lastAction = cvh.lastAction
         if (lastAction == null) {
@@ -86,7 +87,10 @@
             })
             setNegativeButton(
                 android.R.string.cancel,
-                DialogInterface.OnClickListener { dialog, _ -> dialog.cancel() }
+                DialogInterface.OnClickListener { dialog, _ ->
+                    onCancel.invoke()
+                    dialog.cancel()
+                }
             )
         }
         return builder.create().apply {
@@ -111,7 +115,7 @@
     /**
      * AlertDialogs to handle [ControlAction#RESPONSE_CHALLENGE_ACK] response type.
      */
-    fun createConfirmationDialog(cvh: ControlViewHolder): Dialog? {
+    fun createConfirmationDialog(cvh: ControlViewHolder, onCancel: () -> Unit): Dialog? {
         val lastAction = cvh.lastAction
         if (lastAction == null) {
             Log.e(ControlsUiController.TAG,
@@ -130,7 +134,10 @@
             })
             setNegativeButton(
                 android.R.string.cancel,
-                DialogInterface.OnClickListener { dialog, _ -> dialog.cancel() }
+                DialogInterface.OnClickListener { dialog, _ ->
+                    onCancel.invoke()
+                    dialog.cancel()
+                }
             )
         }
         return builder.create().apply {
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 2653ce0..f30756b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -86,6 +86,7 @@
     var behavior: Behavior? = null
     var lastAction: ControlAction? = null
     private var lastChallengeDialog: Dialog? = null
+    private val onDialogCancel: () -> Unit = { lastChallengeDialog = null }
 
     val deviceType: Int
         get() = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType
@@ -154,15 +155,18 @@
                 setTransientStatus(context.resources.getString(R.string.controls_error_failed))
             }
             ControlAction.RESPONSE_CHALLENGE_PIN -> {
-                lastChallengeDialog = ChallengeDialogs.createPinDialog(this, false, failedAttempt)
+                lastChallengeDialog = ChallengeDialogs.createPinDialog(
+                    this, false, failedAttempt, onDialogCancel)
                 lastChallengeDialog?.show()
             }
             ControlAction.RESPONSE_CHALLENGE_PASSPHRASE -> {
-                lastChallengeDialog = ChallengeDialogs.createPinDialog(this, true, failedAttempt)
+                lastChallengeDialog = ChallengeDialogs.createPinDialog(
+                    this, false, failedAttempt, onDialogCancel)
                 lastChallengeDialog?.show()
             }
             ControlAction.RESPONSE_CHALLENGE_ACK -> {
-                lastChallengeDialog = ChallengeDialogs.createConfirmationDialog(this)
+                lastChallengeDialog = ChallengeDialogs.createConfirmationDialog(
+                    this, onDialogCancel)
                 lastChallengeDialog?.show()
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 96494cf..3a37c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -451,7 +451,8 @@
      * @param metadata New metadata.
      */
     @Override
-    public void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
+    public void onPrimaryMetadataOrStateChanged(MediaMetadata metadata,
+            @PlaybackState.State int state) {
         synchronized (this) {
             boolean nextVisible = NotificationMediaManager.isPlayingState(state);
             mMediaHandler.removeCallbacksAndMessages(null);
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 123cf78..9c89fee 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -61,6 +61,18 @@
         return buffer;
     }
 
+    /** Provides a logging buffer for all logs related to managing notification sections. */
+    @Provides
+    @Singleton
+    @NotificationSectionLog
+    public static LogBuffer provideNotificationSectionLogBuffer(
+            LogcatEchoTracker bufferFilter,
+            DumpManager dumpManager) {
+        LogBuffer buffer = new LogBuffer("NotifSectionLog", 500, 10, bufferFilter);
+        buffer.attach(dumpManager);
+        return buffer;
+    }
+
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
     @Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
new file mode 100644
index 0000000..7259eeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for notification sections-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationSectionLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt b/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt
new file mode 100644
index 0000000..2fe0d9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.media
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+
+private val EMPTY_RECT = Rect(0,0,0,0)
+
+private val LAYOUT_CHANGE_LISTENER = object : View.OnLayoutChangeListener {
+
+    override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int,
+                                oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
+        v?.let {
+            if (v.visibility == View.GONE) {
+                v.clipBounds = EMPTY_RECT
+            } else {
+                v.clipBounds = null
+            }
+        }
+    }
+}
+/**
+ * A helper class that clips all GONE children. Useful for transitions in motionlayout which
+ * don't clip its children.
+ */
+class GoneChildrenHideHelper private constructor() {
+    companion object {
+        @JvmStatic
+        fun clipGoneChildrenOnLayout(layout: ViewGroup) {
+            val childCount = layout.childCount
+            for (i in 0 until childCount) {
+                val child = layout.getChildAt(i)
+                child.addOnLayoutChangeListener(LAYOUT_CHANGE_LISTENER)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
index 9374727..7432165 100644
--- a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
@@ -6,6 +6,7 @@
 import android.animation.ValueAnimator
 import android.content.res.ColorStateList
 import android.content.res.Resources
+import android.content.res.TypedArray
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.ColorFilter
@@ -49,6 +50,7 @@
 @Keep
 class IlluminationDrawable : Drawable() {
 
+    private var themeAttrs: IntArray? = null
     private var cornerRadius = 0f
     private var highlightColor = Color.TRANSPARENT
     private val rippleData = RippleData(0f, 0f, 0f, 0f, 0f, 0f, 0f)
@@ -139,13 +141,41 @@
         theme: Resources.Theme?
     ) {
         val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable)
-        cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius)
-        rippleData.minSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMinSize, 0f)
-        rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
-        rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
+        themeAttrs = a.extractThemeAttrs()
+        updateStateFromTypedArray(a)
         a.recycle()
     }
 
+    private fun updateStateFromTypedArray(a: TypedArray) {
+        if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
+            cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius,
+                    cornerRadius)
+        }
+        if (a.hasValue(R.styleable.IlluminationDrawable_rippleMinSize)) {
+            rippleData.minSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMinSize, 0f)
+        }
+        if (a.hasValue(R.styleable.IlluminationDrawable_rippleMaxSize)) {
+            rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
+        }
+        if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
+            rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
+                    100f
+        }
+    }
+
+    override fun canApplyTheme(): Boolean {
+        return themeAttrs != null && themeAttrs!!.size > 0 || super.canApplyTheme()
+    }
+
+    override fun applyTheme(t: Resources.Theme) {
+        super.applyTheme(t)
+        themeAttrs?.let {
+            val a = t.resolveAttributes(it, R.styleable.IlluminationDrawable)
+            updateStateFromTypedArray(a)
+            a.recycle()
+        }
+    }
+
     override fun setColorFilter(p0: ColorFilter?) {
         throw UnsupportedOperationException("Color filters are not supported")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
new file mode 100644
index 0000000..524c695
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.view.View
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.MediaHeaderView
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * A class that controls the media notifications on the lock screen, handles its visibility and
+ * is responsible for the embedding of he media experience.
+ */
+@Singleton
+class KeyguardMediaController @Inject constructor(
+    private val mediaHost: MediaHost,
+    private val bypassController: KeyguardBypassController,
+    private val statusBarStateController: SysuiStatusBarStateController
+) {
+
+    init {
+        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+            override fun onStateChanged(newState: Int) {
+                updateVisibility()
+            }
+        })
+    }
+    private var view: MediaHeaderView? = null
+
+    /**
+     * Attach this controller to a media view, initializing its state
+     */
+    fun attach(mediaView: MediaHeaderView) {
+        view = mediaView
+        // First let's set the desired state that we want for this host
+        mediaHost.visibleChangedListener = { updateVisibility() }
+        mediaHost.expansion = 0.0f
+        mediaHost.showsOnlyActiveMedia = true
+
+        // Let's now initialize this view, which also creates the host view for us.
+        mediaHost.init(MediaHierarchyManager.LOCATION_LOCKSCREEN)
+        mediaView.setContentView(mediaHost.hostView)
+    }
+
+    private fun updateVisibility() {
+        val shouldBeVisible = mediaHost.visible
+                && !bypassController.bypassEnabled
+                && (statusBarStateController.state == StatusBarState.KEYGUARD ||
+                        statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
+        view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt b/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt
new file mode 100644
index 0000000..a366725
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.statusbar.notification.AnimatableProperty
+import com.android.systemui.statusbar.notification.PropertyAnimator
+import com.android.systemui.statusbar.notification.stack.AnimationProperties
+
+/**
+ * A utility class that helps with animations of bound changes designed for motionlayout which
+ * doesn't work together with regular changeBounds.
+ */
+class LayoutAnimationHelper {
+
+    private val layout: ViewGroup
+    private var sizeAnimationPending = false
+    private val desiredBounds = mutableMapOf<View, Rect>()
+    private val animationProperties = AnimationProperties()
+    private val layoutListener = object : View.OnLayoutChangeListener {
+        override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int,
+                                    oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
+            v?.let {
+                if (v.alpha == 0.0f || v.visibility == View.GONE || oldLeft - oldRight == 0 ||
+                        oldTop - oldBottom == 0) {
+                    return
+                }
+                if (oldLeft != left || oldTop != top || oldBottom != bottom || oldRight != right) {
+                    val rect = desiredBounds.getOrPut(v, { Rect() })
+                    rect.set(left, top, right, bottom)
+                    onDesiredLocationChanged(v, rect)
+                }
+            }
+        }
+    }
+
+    constructor(layout: ViewGroup) {
+        this.layout = layout
+        val childCount = this.layout.childCount
+        for (i in 0 until childCount) {
+            val child = this.layout.getChildAt(i)
+            child.addOnLayoutChangeListener(layoutListener)
+        }
+    }
+
+    private fun onDesiredLocationChanged(v: View, rect: Rect) {
+        if (!sizeAnimationPending) {
+            applyBounds(v, rect, animate = false)
+        }
+        // We need to reapply the current bounds in every frame since the layout may override
+        // the layout bounds making this view jump and not all calls to apply bounds actually
+        // reapply them, for example if there's already an animator to the same target
+        reapplyProperty(v, AnimatableProperty.ABSOLUTE_X);
+        reapplyProperty(v, AnimatableProperty.ABSOLUTE_Y);
+        reapplyProperty(v, AnimatableProperty.WIDTH);
+        reapplyProperty(v, AnimatableProperty.HEIGHT);
+    }
+
+    private fun reapplyProperty(v: View, property: AnimatableProperty) {
+        property.property.set(v, property.property.get(v))
+    }
+
+    private fun applyBounds(v: View, newBounds: Rect, animate: Boolean) {
+        PropertyAnimator.setProperty(v, AnimatableProperty.ABSOLUTE_X, newBounds.left.toFloat(),
+                animationProperties, animate)
+        PropertyAnimator.setProperty(v, AnimatableProperty.ABSOLUTE_Y, newBounds.top.toFloat(),
+                animationProperties, animate)
+        PropertyAnimator.setProperty(v, AnimatableProperty.WIDTH, newBounds.width().toFloat(),
+                animationProperties, animate)
+        PropertyAnimator.setProperty(v, AnimatableProperty.HEIGHT, newBounds.height().toFloat(),
+                animationProperties, animate)
+    }
+
+    private fun startBoundAnimation(v: View) {
+        val target = desiredBounds[v] ?: return
+        applyBounds(v, target, animate = true)
+    }
+
+    fun animatePendingSizeChange(duration: Long, delay: Long) {
+        animationProperties.duration = duration
+        animationProperties.delay = delay
+        if (!sizeAnimationPending) {
+            sizeAnimationPending = true
+            layout.viewTreeObserver.addOnPreDrawListener (
+                    object : ViewTreeObserver.OnPreDrawListener {
+                        override fun onPreDraw(): Boolean {
+                            layout.viewTreeObserver.removeOnPreDrawListener(this)
+                            sizeAnimationPending = false
+                            val childCount = layout.childCount
+                            for (i in 0 until childCount) {
+                                val child = layout.getChildAt(i)
+                                startBoundAnimation(child)
+                            }
+                            return true
+                        }
+                    })
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 557132b..c3a7d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -16,10 +16,8 @@
 
 package com.android.systemui.media;
 
-import android.annotation.LayoutRes;
 import android.app.PendingIntent;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -27,35 +25,36 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
-import android.graphics.ImageDecoder;
+import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.ThumbnailUtils;
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
-import android.net.Uri;
 import android.service.media.MediaBrowserService;
-import android.text.TextUtils;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.ViewGroup;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.SeekBar;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.constraintlayout.motion.widget.Key;
+import androidx.constraintlayout.motion.widget.KeyAttributes;
+import androidx.constraintlayout.motion.widget.KeyFrames;
+import androidx.constraintlayout.motion.widget.MotionLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 
+import com.android.settingslib.Utils;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.media.MediaOutputSliceConstants;
@@ -64,24 +63,39 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSMediaBrowser;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
-import java.io.IOException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
- * Base media control panel for System UI
+ * A view controller used for Media Playback.
  */
 public class MediaControlPanel {
     private static final String TAG = "MediaControlPanel";
     @Nullable private final LocalMediaManager mLocalMediaManager;
+
+    // Button IDs for QS controls
+    static final int[] ACTION_IDS = {
+            R.id.action0,
+            R.id.action1,
+            R.id.action2,
+            R.id.action3,
+            R.id.action4
+    };
+
+    private final SeekBarViewModel mSeekBarViewModel;
+    private SeekBarObserver mSeekBarObserver;
     private final Executor mForegroundExecutor;
     protected final Executor mBackgroundExecutor;
     private final ActivityStarter mActivityStarter;
+    private LayoutAnimationHelper mLayoutAnimationHelper;
 
     private Context mContext;
-    protected LinearLayout mMediaNotifView;
-    private View mSeamless;
+    private PlayerViewHolder mViewHolder;
     private MediaSession.Token mToken;
     private MediaController mController;
     private int mForegroundColor;
@@ -89,9 +103,11 @@
     private MediaDevice mDevice;
     protected ComponentName mServiceComponent;
     private boolean mIsRegistered = false;
+    private List<KeyFrames> mKeyFrames;
     private String mKey;
-
-    private final int[] mActionIds;
+    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";
@@ -100,22 +116,6 @@
     private boolean mIsRemotePlayback;
     private QSMediaBrowser mQSMediaBrowser;
 
-    // Button IDs used in notifications
-    protected static final int[] NOTIF_ACTION_IDS = {
-            com.android.internal.R.id.action0,
-            com.android.internal.R.id.action1,
-            com.android.internal.R.id.action2,
-            com.android.internal.R.id.action3,
-            com.android.internal.R.id.action4
-    };
-
-    // URI fields to try loading album art from
-    private static final String[] ART_URIS = {
-            MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
-            MediaMetadata.METADATA_KEY_ART_URI,
-            MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
-    };
-
     private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
         @Override
         public void onSessionDestroyed() {
@@ -135,17 +135,6 @@
         }
     };
 
-    private final OnAttachStateChangeListener mStateListener = new OnAttachStateChangeListener() {
-        @Override
-        public void onViewAttachedToWindow(View unused) {
-            makeActive();
-        }
-        @Override
-        public void onViewDetachedFromWindow(View unused) {
-            makeInactive();
-        }
-    };
-
     private final LocalMediaManager.DeviceCallback mDeviceCallback =
             new LocalMediaManager.DeviceCallback() {
         @Override
@@ -173,40 +162,55 @@
     /**
      * Initialize a new control panel
      * @param context
-     * @param parent
      * @param routeManager Manager used to listen for device change events.
-     * @param layoutId layout resource to use for this control panel
-     * @param actionIds resource IDs for action buttons in the layout
      * @param foregroundExecutor foreground executor
      * @param backgroundExecutor background executor, used for processing artwork
      * @param activityStarter activity starter
      */
-    public MediaControlPanel(Context context, ViewGroup parent,
-            @Nullable LocalMediaManager routeManager, @LayoutRes int layoutId, int[] actionIds,
-            Executor foregroundExecutor, Executor backgroundExecutor,
+    public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager,
+            Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
             ActivityStarter activityStarter) {
         mContext = context;
-        LayoutInflater inflater = LayoutInflater.from(mContext);
-        mMediaNotifView = (LinearLayout) inflater.inflate(layoutId, parent, false);
-        // TODO(b/150854549): removeOnAttachStateChangeListener when this doesn't inflate views
-        // mStateListener shouldn't need to be unregistered since this object shares the same
-        // lifecycle with the inflated view. It would be better, however, if this controller used an
-        // attach/detach of views instead of inflating them in the constructor, which would allow
-        // mStateListener to be unregistered in detach.
-        mMediaNotifView.addOnAttachStateChangeListener(mStateListener);
         mLocalMediaManager = routeManager;
-        mActionIds = actionIds;
         mForegroundExecutor = foregroundExecutor;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
+        mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
+        loadDimens();
+    }
+
+    public void onDestroy() {
+        if (mSeekBarObserver != null) {
+            mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
+        }
+        makeInactive();
+    }
+
+    private void loadDimens() {
+        mAlbumArtRadius = mContext.getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+        mAlbumArtSize = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_album_size);
     }
 
     /**
-     * Get the view used to display media controls
-     * @return the view
+     * Get the view holder used to display media controls
+     * @return the view holder
      */
-    public View getView() {
-        return mMediaNotifView;
+    @Nullable
+    public PlayerViewHolder getView() {
+        return mViewHolder;
+    }
+
+    /**
+     * Sets the listening state of the player.
+     *
+     * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
+     * unnecessary work when the QS panel is closed.
+     *
+     * @param listening True when player should be active. Otherwise, false.
+     */
+    public void setListening(boolean listening) {
+        mSeekBarViewModel.setListening(listening);
     }
 
     /**
@@ -217,21 +221,30 @@
         return mContext;
     }
 
+    /** Attaches the player to the view holder. */
+    public void attach(PlayerViewHolder vh) {
+        mViewHolder = vh;
+        MotionLayout motionView = vh.getPlayer();
+        mLayoutAnimationHelper = new LayoutAnimationHelper(motionView);
+        GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView);
+        mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList();
+        mSeekBarObserver = new SeekBarObserver(motionView);
+        mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
+        SeekBar bar = vh.getSeekBar();
+        bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
+        bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
+    }
+
     /**
-     * Update the media panel view for the given media session
-     * @param token
-     * @param iconDrawable
-     * @param largeIcon
-     * @param iconColor
-     * @param bgColor
-     * @param contentIntent
-     * @param appNameString
-     * @param key
+     * Bind this view based on the data given
      */
-    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
-            int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
-            String key) {
-        // Ensure that component names are updated if token has changed
+    public void bind(@NotNull MediaData data) {
+        if (mViewHolder == null) {
+            return;
+        }
+        MediaSession.Token token = data.getToken();
+        mForegroundColor = data.getForegroundColor();
+        mBackgroundColor = data.getBackgroundColor();
         if (mToken == null || !mToken.equals(token)) {
             if (mQSMediaBrowser != null) {
                 Log.d(TAG, "Disconnecting old media browser");
@@ -243,20 +256,21 @@
             mCheckedForResumption = false;
         }
 
-        mForegroundColor = iconColor;
-        mBackgroundColor = bgColor;
         mController = new MediaController(mContext, mToken);
-        mKey = key;
+
+        ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded);
+        ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed);
 
         // 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 = mController.getPackageName();
+        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())) {
@@ -271,38 +285,61 @@
 
         mController.registerCallback(mSessionCallback);
 
-        mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
+        mViewHolder.getBackground().setBackgroundTintList(
+                ColorStateList.valueOf(mBackgroundColor));
 
         // Click action
-        if (contentIntent != null) {
-            mMediaNotifView.setOnClickListener(v -> {
-                mActivityStarter.postStartActivityDismissingKeyguard(contentIntent);
+        PendingIntent clickIntent = data.getClickIntent();
+        if (clickIntent != null) {
+            mViewHolder.getPlayer().setOnClickListener(v -> {
+                mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
             });
         }
 
+        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);
+
         // App icon
-        ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
+        ImageView appIcon = mViewHolder.getAppIcon();
+        Drawable iconDrawable = data.getAppIcon().mutate();
         iconDrawable.setTint(mForegroundColor);
         appIcon.setImageDrawable(iconDrawable);
 
+        // Song name
+        TextView titleText = mViewHolder.getTitleText();
+        titleText.setText(data.getSong());
+        titleText.setTextColor(data.getForegroundColor());
+
+        // App title
+        TextView appName = mViewHolder.getAppName();
+        appName.setText(data.getApp());
+        appName.setTextColor(mForegroundColor);
+
+        // Artist name
+        TextView artistText = mViewHolder.getArtistText();
+        artistText.setText(data.getArtist());
+        artistText.setTextColor(mForegroundColor);
+
         // Transfer chip
-        mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
-        if (mSeamless != null) {
-            if (mLocalMediaManager != null) {
-                mSeamless.setVisibility(View.VISIBLE);
-                updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
-                mSeamless.setOnClickListener(v -> {
-                    final Intent intent = new Intent()
-                            .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
-                            .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
-                                    mController.getPackageName())
-                            .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
-                    mActivityStarter.startActivity(intent, false, true /* dismissShade */,
-                            Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-                });
-            } else {
-                Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
-            }
+        if (mLocalMediaManager != null) {
+            mViewHolder.getSeamless().setVisibility(View.VISIBLE);
+            setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
+            setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
+            updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
+            mViewHolder.getSeamless().setOnClickListener(v -> {
+                final Intent intent = new Intent()
+                        .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
+                        .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
+                                mController.getPackageName())
+                        .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
+                mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            });
+        } else {
+            Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
         }
         PlaybackInfo playbackInfo = mController.getPlaybackInfo();
         if (playbackInfo != null) {
@@ -311,43 +348,113 @@
             Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback.");
             mIsRemotePlayback = false;
         }
+        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
+        // Media controls
+        int i = 0;
+        List<MediaAction> actionIcons = data.getActions();
+        for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
+            int actionId = ACTION_IDS[i];
+            final ImageButton button = mViewHolder.getAction(actionId);
+            MediaAction mediaAction = actionIcons.get(i);
+            button.setImageDrawable(mediaAction.getDrawable());
+            button.setContentDescription(mediaAction.getContentDescription());
+            button.setImageTintList(ColorStateList.valueOf(mForegroundColor));
+            PendingIntent actionIntent = mediaAction.getIntent();
+
+            if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) {
+                ((IlluminationDrawable) mViewHolder.getBackground().getBackground())
+                        .setupTouch(button, mViewHolder.getPlayer());
+            }
+
+            button.setOnClickListener(v -> {
+                if (actionIntent != null) {
+                    try {
+                        actionIntent.send();
+                    } catch (PendingIntent.CanceledException e) {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
+            updateKeyFrameVisibility(actionId, visibleInCompat);
+            setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+            setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
+        }
+
+        // Hide any unused buttons
+        for (; i < ACTION_IDS.length; i++) {
+            setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */);
+            setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */);
+        }
+
+        // Seek Bar
+        final MediaController controller = getController();
+        mBackgroundExecutor.execute(
+                () -> mSeekBarViewModel.updateController(controller, data.getForegroundColor()));
+
+        // Set up long press menu
+        // TODO: b/156036025 bring back media guts
 
         makeActive();
 
-        // App title (not in mini player)
-        TextView appName = mMediaNotifView.findViewById(R.id.app_name);
-        if (appName != null) {
-            appName.setText(appNameString);
-            appName.setTextColor(mForegroundColor);
+        // Update both constraint sets to regenerate the animation.
+        mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet);
+        mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet);
+    }
+
+    @UiThread
+    private Drawable createRoundedBitmap(Icon icon) {
+        if (icon == null) {
+            return null;
         }
-
-        // Can be null!
-        MediaMetadata mediaMetadata = mController.getMetadata();
-
-        ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
-        if (albumView != null) {
-            // Resize art in a background thread
-            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
+        // Let's scale down the View, such that the content always nicely fills the view.
+        // ThumbnailUtils actually scales it down such that it may not be filled for odd aspect
+        // ratios
+        Drawable drawable = icon.loadDrawable(mContext);
+        float aspectRatio = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth();
+        Rect bounds;
+        if (aspectRatio > 1.0f) {
+            bounds = new Rect(0, 0, mAlbumArtSize, (int) (mAlbumArtSize * aspectRatio));
+        } else {
+            bounds = new Rect(0, 0, (int) (mAlbumArtSize / aspectRatio), mAlbumArtSize);
         }
-
-        // Song name
-        TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
-        String songName = "";
-        if (mediaMetadata != null) {
-            songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+        if (bounds.width() > mAlbumArtSize || bounds.height() > mAlbumArtSize) {
+            float offsetX = (bounds.width() - mAlbumArtSize) / 2.0f;
+            float offsetY = (bounds.height() - mAlbumArtSize) / 2.0f;
+            bounds.offset((int) -offsetX,(int) -offsetY);
         }
-        titleText.setText(songName);
-        titleText.setTextColor(mForegroundColor);
+        drawable.setBounds(bounds);
+        Bitmap scaled = Bitmap.createBitmap(mAlbumArtSize, mAlbumArtSize,
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(scaled);
+        drawable.draw(canvas);
+        RoundedBitmapDrawable artwork = RoundedBitmapDrawableFactory.create(
+                mContext.getResources(), scaled);
+        artwork.setCornerRadius(mAlbumArtRadius);
+        return artwork;
+    }
 
-        // Artist name (not in mini player)
-        TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
-        if (artistText != null) {
-            String artistName = "";
-            if (mediaMetadata != null) {
-                artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+    /**
+     * Updates the keyframe visibility such that only views that are not visible actually go
+     * through a transition and fade in.
+     *
+     * @param actionId the id to change
+     * @param visible is the view visible
+     */
+    private void updateKeyFrameVisibility(int actionId, boolean visible) {
+        if (mKeyFrames == null) {
+            return;
+        }
+        for (int i = 0; i < mKeyFrames.size(); i++) {
+            KeyFrames keyframe = mKeyFrames.get(i);
+            ArrayList<Key> viewKeyFrames = keyframe.getKeyFramesForView(actionId);
+            for (int j = 0; j < viewKeyFrames.size(); j++) {
+                Key key = viewKeyFrames.get(j);
+                if (key instanceof KeyAttributes) {
+                    KeyAttributes attributes = (KeyAttributes) key;
+                    attributes.setValue("alpha", visible ? 1.0f : 0.0f);
+                }
             }
-            artistText.setText(artistName);
-            artistText.setTextColor(mForegroundColor);
         }
     }
 
@@ -421,156 +528,42 @@
     }
 
     /**
-     * Process album art for layout
-     * @param description media description
-     * @param albumView view to hold the album art
-     */
-    protected void processAlbumArt(MediaDescription description, ImageView albumView) {
-        Bitmap albumArt = null;
-
-        // First try loading from URI
-        albumArt = loadBitmapFromUri(description.getIconUri());
-
-        // Then check bitmap
-        if (albumArt == null) {
-            albumArt = description.getIconBitmap();
-        }
-
-        processAlbumArtInternal(albumArt, albumView);
-    }
-
-    /**
-     * Process album art for layout
-     * @param metadata media metadata
-     * @param largeIcon from notification, checked as a fallback if metadata does not have art
-     * @param albumView view to hold the album art
-     */
-    private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) {
-        Bitmap albumArt = null;
-
-        if (metadata != null) {
-            // First look in URI fields
-            for (String field : ART_URIS) {
-                String uriString = metadata.getString(field);
-                if (!TextUtils.isEmpty(uriString)) {
-                    albumArt = loadBitmapFromUri(Uri.parse(uriString));
-                    if (albumArt != null) {
-                        Log.d(TAG, "loaded art from " + field);
-                        break;
-                    }
-                }
-            }
-
-            // Then check bitmap field
-            if (albumArt == null) {
-                albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-            }
-        }
-
-        // Finally try the notification's largeIcon
-        if (albumArt == null && largeIcon != null) {
-            albumArt = largeIcon.getBitmap();
-        }
-
-        processAlbumArtInternal(albumArt, albumView);
-    }
-
-    /**
-     * Load a bitmap from a URI
-     * @param uri
-     * @return bitmap, or null if couldn't be loaded
-     */
-    private Bitmap loadBitmapFromUri(Uri uri) {
-        // ImageDecoder requires a scheme of the following types
-        if (uri.getScheme() == null) {
-            return null;
-        }
-
-        if (!uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
-                && !uri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)
-                && !uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
-            return null;
-        }
-
-        ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri);
-        try {
-            return ImageDecoder.decodeBitmap(source);
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Resize and crop the image if provided and update the control view
-     * @param albumArt Bitmap of art to display, or null to hide view
-     * @param albumView View that will hold the art
-     */
-    private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) {
-        // Resize
-        RoundedBitmapDrawable roundedDrawable = null;
-        if (albumArt != null) {
-            float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
-            Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
-            int albumSize = (int) mContext.getResources().getDimension(
-                    R.dimen.qs_media_album_size);
-            Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
-            roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
-            roundedDrawable.setCornerRadius(radius);
-        } else {
-            Log.e(TAG, "No album art available");
-        }
-
-        // Now that it's resized, update the UI
-        final RoundedBitmapDrawable result = roundedDrawable;
-        mForegroundExecutor.execute(() -> {
-            if (result != null) {
-                albumView.setImageDrawable(result);
-                albumView.setVisibility(View.VISIBLE);
-            } else {
-                albumView.setImageDrawable(null);
-                albumView.setVisibility(View.GONE);
-            }
-        });
-    }
-
-    /**
      * Update the current device information
      * @param device device information to display
      */
     private void updateDevice(MediaDevice device) {
-        if (mSeamless == null) {
-            return;
-        }
         mForegroundExecutor.execute(() -> {
             updateChipInternal(device);
         });
     }
 
     private void updateChipInternal(MediaDevice device) {
+        if (mViewHolder == null) {
+            return;
+        }
         ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);
 
         // Update the outline color
-        LinearLayout viewLayout = (LinearLayout) mSeamless;
+        LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless();
         RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
         GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
         rect.setStroke(2, mForegroundColor);
         rect.setColor(mBackgroundColor);
 
-        ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image);
-        TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text);
+        ImageView iconView = mViewHolder.getSeamlessIcon();
+        TextView deviceName = mViewHolder.getSeamlessText();
         deviceName.setTextColor(fgTintList);
 
         if (mIsRemotePlayback) {
-            mSeamless.setEnabled(false);
-            mSeamless.setAlpha(0.38f);
+            mViewHolder.getSeamless().setEnabled(false);
+            mViewHolder.getSeamless().setAlpha(0.38f);
             iconView.setImageResource(R.drawable.ic_hardware_speaker);
             iconView.setVisibility(View.VISIBLE);
             iconView.setImageTintList(fgTintList);
             deviceName.setText(R.string.media_seamless_remote_device);
         } else if (device != null) {
-            mSeamless.setEnabled(true);
-            mSeamless.setAlpha(1f);
+            mViewHolder.getSeamless().setEnabled(true);
+            mViewHolder.getSeamless().setAlpha(1f);
             Drawable icon = device.getIcon();
             iconView.setVisibility(View.VISIBLE);
             iconView.setImageTintList(fgTintList);
@@ -586,8 +579,8 @@
         } else {
             // Reset to default
             Log.d(TAG, "device is null. Not binding output chip.");
-            mSeamless.setEnabled(true);
-            mSeamless.setAlpha(1f);
+            mViewHolder.getSeamless().setEnabled(true);
+            mViewHolder.getSeamless().setAlpha(1f);
             iconView.setVisibility(View.GONE);
             deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
         }
@@ -612,16 +605,20 @@
      * Hide the media buttons and show only a restart button
      */
     protected void resetButtons() {
+        if (mViewHolder == null) {
+            return;
+        }
         // Hide all the old buttons
-        for (int i = 0; i < mActionIds.length; i++) {
-            ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
-            if (thisBtn != null) {
-                thisBtn.setVisibility(View.GONE);
-            }
+
+        ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded);
+        ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed);
+        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 = mMediaNotifView.findViewById(mActionIds[0]);
+        ImageButton btn = mViewHolder.getAction0();
         btn.setOnClickListener(v -> {
             Log.d(TAG, "Attempting to restart session");
             if (mQSMediaBrowser != null) {
@@ -643,7 +640,25 @@
         });
         btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
         btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
-        btn.setVisibility(View.VISIBLE);
+        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
+        });
+    }
+
+    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);
     }
 
     private void makeActive() {
@@ -667,7 +682,6 @@
             mIsRegistered = false;
         }
     }
-
     /**
      * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
      * component to the list of resumption components
@@ -739,4 +753,33 @@
      * Called when a player can't be resumed to give it an opportunity to hide or remove itself
      */
     protected void removePlayer() { }
+
+    public void measure(@Nullable MediaMeasurementInput input) {
+        if (mViewHolder == null) {
+            return;
+        }
+        if (input != null) {
+            int width = input.getWidth();
+            setPlayerWidth(width);
+            mViewHolder.getPlayer().measure(input.getWidthMeasureSpec(),
+                    input.getHeightMeasureSpec());
+        }
+    }
+
+    public void setPlayerWidth(int width) {
+        if (mViewHolder == null) {
+            return;
+        }
+        MotionLayout view = mViewHolder.getPlayer();
+        ConstraintSet expandedSet = view.getConstraintSet(R.id.expanded);
+        ConstraintSet collapsedSet = view.getConstraintSet(R.id.collapsed);
+        collapsedSet.setGuidelineBegin(R.id.view_width, width);
+        expandedSet.setGuidelineBegin(R.id.view_width, width);
+        view.updateState(R.id.collapsed, collapsedSet);
+        view.updateState(R.id.expanded, expandedSet);
+    }
+
+    public void animatePendingSizeChange(long duration, long startDelay) {
+        mLayoutAnimationHelper.animatePendingSizeChange(duration, startDelay);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
new file mode 100644
index 0000000..6a26461
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.media.session.MediaSession
+
+/** State of a media view. */
+data class MediaData(
+    val initialized: Boolean = false,
+    val foregroundColor: Int,
+    val backgroundColor: Int,
+    val app: String?,
+    val appIcon: Drawable?,
+    val artist: CharSequence?,
+    val song: CharSequence?,
+    val artwork: Icon?,
+    val actions: List<MediaAction>,
+    val actionsToShowInCompact: List<Int>,
+    val packageName: String?,
+    val token: MediaSession.Token?,
+    val clickIntent: PendingIntent?
+)
+
+/** State of a media action. */
+data class MediaAction(
+    val drawable: Drawable?,
+    val intent: PendingIntent?,
+    val contentDescription: CharSequence?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
new file mode 100644
index 0000000..8da864c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -0,0 +1,297 @@
+/*
+ * 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.Notification
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.ImageDecoder
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.net.Uri
+import android.service.notification.StatusBarNotification
+import android.text.TextUtils
+import android.util.Log
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.MediaNotificationProcessor
+import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.Utils
+import java.io.IOException
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.collections.LinkedHashMap
+
+// URI fields to try loading album art from
+private val ART_URIS = arrayOf(
+        MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+        MediaMetadata.METADATA_KEY_ART_URI,
+        MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+)
+
+private const val TAG = "MediaDataManager"
+
+private val LOADING = MediaData(false, 0, 0, null, null, null, null, null,
+        emptyList(), emptyList(), null, null, null)
+
+/**
+ * A class that facilitates management and loading of Media Data, ready for binding.
+ */
+@Singleton
+class MediaDataManager @Inject constructor(
+    private val context: Context,
+    private val mediaControllerFactory: MediaControllerFactory,
+    @Background private val backgroundExecutor: Executor,
+    @Main private val foregroundExcecutor: Executor
+) {
+
+    private val listeners: MutableSet<Listener> = mutableSetOf()
+    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+
+    fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
+        if (isMediaNotification(sbn)) {
+            if (!mediaEntries.containsKey(key)) {
+                mediaEntries.put(key, LOADING)
+            }
+            loadMediaData(key, sbn)
+        } else {
+            onNotificationRemoved(key)
+        }
+    }
+
+    private fun loadMediaData(key: String, sbn: StatusBarNotification) {
+        backgroundExecutor.execute {
+            loadMediaDataInBg(key, sbn)
+        }
+    }
+
+    /**
+     * Add a listener for changes in this class
+     */
+    fun addListener(listener: Listener) = listeners.add(listener)
+
+    /**
+     * Remove a listener for changes in this class
+     */
+    fun removeListener(listener: Listener) = listeners.remove(listener)
+
+    private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) {
+        val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
+                as MediaSession.Token?
+        val metadata = mediaControllerFactory.create(token).metadata
+
+        if (metadata == null) {
+            // TODO: handle this better, removing media notification
+            return
+        }
+
+        // Foreground and Background colors computed from album art
+        val notif: Notification = sbn.notification
+        var fgColor = notif.color
+        var bgColor = -1
+        var artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART)
+        if (artworkBitmap == null) {
+            artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+        }
+        if (artworkBitmap == null) {
+            artworkBitmap = loadBitmapFromUri(metadata)
+        }
+        val artWorkIcon = if (artworkBitmap == null) {
+            notif.getLargeIcon()
+        } else {
+            Icon.createWithBitmap(artworkBitmap)
+        }
+        if (artWorkIcon != null) {
+            // If we have art, get colors from that
+            if (artworkBitmap == null) {
+                if (artWorkIcon.type == Icon.TYPE_BITMAP ||
+                        artWorkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP) {
+                    artworkBitmap = artWorkIcon.bitmap
+                } else {
+                    val drawable: Drawable = artWorkIcon.loadDrawable(context)
+                    artworkBitmap = Bitmap.createBitmap(
+                            drawable.intrinsicWidth,
+                            drawable.intrinsicHeight,
+                            Bitmap.Config.ARGB_8888)
+                    val canvas = Canvas(artworkBitmap)
+                    drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+                    drawable.draw(canvas)
+                }
+            }
+            val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
+                    .generate()
+            val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
+            bgColor = swatch.rgb
+            fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p)
+        }
+        // Make sure colors will be legible
+        val isDark = !ContrastColorUtil.isColorLight(bgColor)
+        fgColor = ContrastColorUtil.resolveContrastColor(context, fgColor, bgColor,
+                isDark)
+        fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark)
+
+        // App name
+        val builder = Notification.Builder.recoverBuilder(context, notif)
+        val app = builder.loadHeaderAppName()
+
+        // App Icon
+        val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context)
+
+        // Song name
+        var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+        if (song == null) {
+            song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
+        }
+        if (song == null) {
+            song = HybridGroupManager.resolveTitle(notif)
+        }
+
+        // Artist name
+        var artist: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
+        if (artist == null) {
+            artist = HybridGroupManager.resolveText(notif)
+        }
+
+        // Control buttons
+        val actionIcons: MutableList<MediaAction> = ArrayList()
+        val actions = notif.actions
+        val actionsToShowCollapsed = notif.extras.getIntArray(
+                Notification.EXTRA_COMPACT_ACTIONS)?.toList() ?: emptyList()
+        // TODO: b/153736623 look into creating actions when this isn't a media style notification
+
+        val packageContext: Context = sbn.getPackageContext(context)
+        for (action in actions) {
+            val mediaAction = MediaAction(
+                    action.getIcon().loadDrawable(packageContext),
+                    action.actionIntent,
+                    action.title)
+            actionIcons.add(mediaAction)
+        }
+
+        foregroundExcecutor.execute {
+            onMediaDataLoaded(key, MediaData(true, fgColor, bgColor, app, smallIconDrawable, artist,
+                    song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
+                    notif.contentIntent))
+        }
+    }
+
+    /**
+     * Load a bitmap from the various Art metadata URIs
+     */
+    private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
+        for (uri in ART_URIS) {
+            val uriString = metadata.getString(uri)
+            if (!TextUtils.isEmpty(uriString)) {
+                val albumArt = loadBitmapFromUri(Uri.parse(uriString))
+                if (albumArt != null) {
+                    Log.d(TAG, "loaded art from $uri")
+                    break
+                }
+            }
+        }
+        return null
+    }
+
+    /**
+     * Load a bitmap from a URI
+     * @param uri the uri to load
+     * @return bitmap, or null if couldn't be loaded
+     */
+    private fun loadBitmapFromUri(uri: Uri): Bitmap? {
+        // ImageDecoder requires a scheme of the following types
+        if (uri.scheme == null) {
+            return null
+        }
+
+        if (!uri.scheme.equals(ContentResolver.SCHEME_CONTENT) &&
+                !uri.scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE) &&
+                !uri.scheme.equals(ContentResolver.SCHEME_FILE)) {
+            return null
+        }
+
+        val source = ImageDecoder.createSource(context.getContentResolver(), uri)
+        return try {
+            ImageDecoder.decodeBitmap(source)
+        } catch (e: IOException) {
+            e.printStackTrace()
+            null
+        }
+    }
+
+    fun onMediaDataLoaded(key: String, data: MediaData) {
+        if (mediaEntries.containsKey(key)) {
+            // Otherwise this was removed already
+            mediaEntries.put(key, data)
+            listeners.forEach {
+                it.onMediaDataLoaded(key, data)
+            }
+        }
+    }
+
+    fun onNotificationRemoved(key: String) {
+        val removed = mediaEntries.remove(key)
+        if (removed != null) {
+            listeners.forEach {
+                it.onMediaDataRemoved(key)
+            }
+        }
+    }
+
+    private fun isMediaNotification(sbn: StatusBarNotification): Boolean {
+        if (!Utils.useQsMediaPlayer(context)) {
+            return false
+        }
+        if (!sbn.notification.hasMediaSession()) {
+            return false
+        }
+        val notificationStyle = sbn.notification.notificationStyle
+        if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
+                Notification.MediaStyle::class.java.equals(notificationStyle)) {
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Are there any media notifications active?
+     */
+    fun hasActiveMedia() = mediaEntries.size > 0
+
+    fun hasAnyMedia(): Boolean {
+        // TODO: implement this when we implemented resumption
+        return hasActiveMedia()
+    }
+
+    interface Listener {
+
+        /**
+         * Called whenever there's new MediaData Loaded for the consumption in views
+         */
+        fun onMediaDataLoaded(key: String, data: MediaData) {}
+
+        /**
+         * Called whenever a previously existing Media notification was removed
+         */
+        fun onMediaDataRemoved(key: String) {}
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
new file mode 100644
index 0000000..6b1c520
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -0,0 +1,425 @@
+/*
+ * 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.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.annotation.IntDef
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import com.android.systemui.Interpolators
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.animation.UniqueObjectHostView
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * This manager is responsible for placement of the unique media view between the different hosts
+ * and animate the positions of the views to achieve seamless transitions.
+ */
+@Singleton
+class MediaHierarchyManager @Inject constructor(
+    private val context: Context,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    private val keyguardStateController: KeyguardStateController,
+    private val bypassController: KeyguardBypassController,
+    private val mediaViewManager: MediaViewManager,
+    private val mediaMeasurementProvider: MediaMeasurementManager
+) {
+    /**
+     * The root overlay of the hierarchy. This is where the media notification is attached to
+     * whenever the view is transitioning from one host to another. It also make sure that the
+     * view is always in its final state when it is attached to a view host.
+     */
+    private var rootOverlay: ViewGroupOverlay? = null
+    private lateinit var currentState: MediaState
+    private val mediaCarousel
+        get() =  mediaViewManager.mediaCarousel
+    private var animationStartState: MediaState? = null
+    private var statusbarState: Int = statusBarStateController.state
+    private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
+        interpolator = Interpolators.FAST_OUT_SLOW_IN
+        addUpdateListener {
+            updateTargetState()
+            applyState(animationStartState!!.interpolate(targetState!!, animatedFraction))
+        }
+        addListener(object : AnimatorListenerAdapter() {
+            private var cancelled: Boolean = false
+
+            override fun onAnimationCancel(animation: Animator?) {
+                cancelled = true
+            }
+            override fun onAnimationEnd(animation: Animator?) {
+                if (!cancelled) {
+                    applyTargetStateIfNotAnimating()
+                }
+            }
+
+            override fun onAnimationStart(animation: Animator?) {
+                cancelled = false
+            }
+        })
+    }
+    private var targetState: MediaState? = null
+    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
+
+    /**
+     * The last location where this view was at before going to the desired location. This is
+     * useful for guided transitions.
+     */
+    @MediaLocation private var previousLocation = -1
+
+    /**
+     * The desired location where the view will be at the end of the transition.
+     */
+    @MediaLocation private var desiredLocation = -1
+
+    /**
+     * The current attachment location where the view is currently attached.
+     * Usually this matches the desired location except for animations whenever a view moves
+     * to the new desired location, during which it is in [IN_OVERLAY].
+     */
+    @MediaLocation private var currentAttachmentLocation = -1
+
+    var qsExpansion: Float = 0.0f
+        set(value) {
+            if (field != value) {
+                field = value
+                updateDesiredLocation()
+                if (getQSTransformationProgress() >= 0) {
+                    updateTargetState()
+                    applyTargetStateIfNotAnimating()
+                }
+            }
+        }
+
+    init {
+        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+            override fun onStatePreChange(oldState: Int, newState: Int) {
+                // We're updating the location before the state change happens, since we want the
+                // location of the previous state to still be up to date when the animation starts
+                statusbarState = newState
+                updateDesiredLocation()
+            }
+
+            override fun onStateChanged(newState: Int) {
+                updateTargetState()
+            }
+        })
+    }
+
+    /**
+     * Register a media host and create a view can be attached to a view hierarchy
+     * and where the players will be placed in when the host is the currently desired state.
+     *
+     * @return the hostView associated with this location
+     */
+    fun register(mediaObject: MediaHost) : ViewGroup {
+        val viewHost = createUniqueObjectHost(mediaObject)
+        mediaObject.hostView = viewHost;
+        mediaHosts[mediaObject.location] = mediaObject
+        if (mediaObject.location == desiredLocation) {
+            // In case we are overriding a view that is already visible, make sure we attach it
+            // to this new host view in the below call
+            desiredLocation = -1
+        }
+        if (mediaObject.location == currentAttachmentLocation) {
+            currentAttachmentLocation = -1
+        }
+        updateDesiredLocation()
+        return viewHost
+    }
+
+    private fun createUniqueObjectHost(host: MediaHost): UniqueObjectHostView {
+        val viewHost = UniqueObjectHostView(context)
+        viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host)
+        viewHost.onMeasureListener =  { input ->
+            if (host.location == desiredLocation) {
+                // Measurement of the currently active player is happening, Let's make
+                // sure the player width is up to date
+                val measuringInput = host.getMeasuringInput(input)
+                mediaViewManager.setPlayerWidth(measuringInput.width)
+            }
+        }
+
+        viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(p0: View?) {
+                if (rootOverlay == null) {
+                    rootOverlay = (viewHost.viewRootImpl.view.overlay as ViewGroupOverlay)
+                }
+                viewHost.removeOnAttachStateChangeListener(this)
+            }
+
+            override fun onViewDetachedFromWindow(p0: View?) {
+            }
+        })
+        return viewHost
+    }
+
+    /**
+     * Updates the location that the view should be in. If it changes, an animation may be triggered
+     * going from the old desired location to the new one.
+     */
+    private fun updateDesiredLocation() {
+        val desiredLocation = calculateLocation()
+        if (desiredLocation != this.desiredLocation) {
+            if (this.desiredLocation >= 0) {
+                previousLocation = this.desiredLocation
+            }
+            val isNewView = this.desiredLocation == -1
+            this.desiredLocation = desiredLocation
+            // Let's perform a transition
+            val animate = shouldAnimateTransition(desiredLocation, previousLocation)
+            val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
+            mediaViewManager.onDesiredLocationChanged(getHost(desiredLocation)?.currentState,
+                    animate, animDuration, delay)
+            performTransitionToNewLocation(isNewView, animate)
+        }
+    }
+
+    private fun performTransitionToNewLocation(isNewView: Boolean, animate: Boolean) {
+        if (previousLocation < 0 || isNewView) {
+            cancelAnimationAndApplyDesiredState()
+            return
+        }
+        val currentHost = getHost(desiredLocation)
+        val previousHost = getHost(previousLocation)
+        if (currentHost == null || previousHost == null) {
+            cancelAnimationAndApplyDesiredState()
+            return
+        }
+        updateTargetState()
+        if (isCurrentlyInGuidedTransformation()) {
+            applyTargetStateIfNotAnimating()
+        } else if (animate) {
+            animator.cancel()
+            if (currentAttachmentLocation == IN_OVERLAY
+                    || !previousHost.hostView.isAttachedToWindow) {
+                // Let's animate to the new position, starting from the current position
+                // We also go in here in case the view was detached, since the bounds wouldn't
+                // be correct anymore
+                animationStartState = currentState.copy()
+            } else {
+                // otherwise, let's take the freshest state, since the current one could
+                // be outdated
+                animationStartState = previousHost.currentState.copy()
+            }
+            adjustAnimatorForTransition(desiredLocation, previousLocation)
+            animator.start()
+        } else {
+            cancelAnimationAndApplyDesiredState()
+        }
+    }
+
+    private fun shouldAnimateTransition(
+        @MediaLocation currentLocation: Int,
+        @MediaLocation previousLocation: Int
+    ): Boolean {
+        if (currentLocation == LOCATION_QQS
+                && previousLocation == LOCATION_LOCKSCREEN
+                && (statusBarStateController.leaveOpenOnKeyguardHide()
+                        || statusbarState == StatusBarState.SHADE_LOCKED)) {
+            // Usually listening to the isShown is enough to determine this, but there is some
+            // non-trivial reattaching logic happening that will make the view not-shown earlier
+            return true
+        }
+        return mediaCarousel.isShown || animator.isRunning
+    }
+
+    private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
+        val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
+        animator.apply {
+            duration  = animDuration
+            startDelay = delay
+        }
+
+    }
+
+    private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair<Long, Long> {
+        var animDuration = 200L
+        var delay = 0L
+        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
+            // Going to the full shade, let's adjust the animation duration
+            if (statusbarState == StatusBarState.SHADE
+                    && keyguardStateController.isKeyguardFadingAway) {
+                delay = keyguardStateController.keyguardFadingAwayDelay
+            }
+            animDuration = StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE.toLong()
+        } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
+            animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
+        }
+        return animDuration to delay
+    }
+
+    private fun applyTargetStateIfNotAnimating() {
+        if (!animator.isRunning) {
+            // Let's immediately apply the target state (which is interpolated) if there is
+            // no animation running. Otherwise the animation update will already update
+            // the location
+            applyState(targetState!!)
+        }
+    }
+
+    /**
+     * Updates the state that the view wants to be in at the end of the animation.
+     */
+    private fun updateTargetState() {
+        if (isCurrentlyInGuidedTransformation()) {
+            val progress = getTransformationProgress()
+            val currentHost = getHost(desiredLocation)!!
+            val previousHost = getHost(previousLocation)!!
+            val newState = currentHost.currentState
+            val previousState = previousHost.currentState
+            targetState = previousState.interpolate(newState, progress)
+        } else {
+            targetState = getHost(desiredLocation)?.currentState
+        }
+    }
+
+    /**
+     * @return true if this transformation is guided by an external progress like a finger
+     */
+    private fun isCurrentlyInGuidedTransformation() : Boolean {
+        return getTransformationProgress() >= 0
+    }
+
+    /**
+     * @return the current transformation progress if we're in a guided transformation and -1 
+     * otherwise
+     */
+    private fun getTransformationProgress(): Float {
+        val progress = getQSTransformationProgress()
+        if (progress >= 0) {
+            return progress
+        }
+        return -1.0f
+    }
+
+    private fun getQSTransformationProgress(): Float {
+        val currentHost = getHost(desiredLocation)
+        val previousHost = getHost(previousLocation)
+        if (currentHost?.location == LOCATION_QS) {
+            if (previousHost?.location == LOCATION_QQS) {
+                return qsExpansion
+            }
+        }
+        return -1.0f
+    }
+
+    private fun getHost(@MediaLocation location: Int): MediaHost? {
+        if (location < 0) {
+            return null
+        }
+        return mediaHosts[location]
+    }
+
+    private fun cancelAnimationAndApplyDesiredState() {
+        animator.cancel()
+        getHost(desiredLocation)?.let {
+            applyState(it.currentState)
+        }
+    }
+
+    private fun applyState(state: MediaState) {
+        currentState = state.copy()
+        mediaViewManager.setCurrentState(currentState)
+        updateHostAttachment()
+        if (currentAttachmentLocation == IN_OVERLAY) {
+            val boundsOnScreen = state.boundsOnScreen
+            mediaCarousel.setLeftTopRightBottom(
+                    boundsOnScreen.left,
+                    boundsOnScreen.top,
+                    boundsOnScreen.right,
+                    boundsOnScreen.bottom)
+        }
+    }
+
+    private fun updateHostAttachment() {
+        val inOverlay = isTransitionRunning() && rootOverlay != null
+        val newLocation = if (inOverlay) IN_OVERLAY else desiredLocation
+        if (currentAttachmentLocation != newLocation) {
+            currentAttachmentLocation = newLocation
+
+            // Remove the carousel from the old host
+            (mediaCarousel.parent as ViewGroup?)?.removeView(mediaCarousel)
+
+            // Add it to the new one
+            val targetHost = getHost(desiredLocation)!!.hostView
+            if (inOverlay) {
+                rootOverlay!!.add(mediaCarousel)
+            } else {
+                targetHost.addView(mediaCarousel)
+                mediaViewManager.onViewReattached()
+            }
+        }
+    }
+
+    private fun isTransitionRunning(): Boolean {
+        return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f
+                || animator.isRunning
+    }
+
+    @MediaLocation
+    private fun calculateLocation() : Int {
+        val onLockscreen = (!bypassController.bypassEnabled
+                && (statusbarState == StatusBarState.KEYGUARD
+                || statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+        return when {
+            qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
+            qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
+            onLockscreen -> LOCATION_LOCKSCREEN
+            else -> LOCATION_QQS
+        }
+    }
+
+    /**
+     * The expansion of quick settings
+     */
+    @IntDef(prefix = ["LOCATION_"], value = [LOCATION_QS, LOCATION_QQS, LOCATION_LOCKSCREEN])
+    @Retention(AnnotationRetention.SOURCE)
+    annotation class MediaLocation
+
+    companion object {
+        /**
+         * Attached in expanded quick settings
+         */
+        const val LOCATION_QS = 0
+
+        /**
+         * Attached in the collapsed QS
+         */
+        const val LOCATION_QQS = 1
+
+        /**
+         * Attached on the lock screen
+         */
+        const val LOCATION_LOCKSCREEN = 2
+
+        /**
+         * Attached at the root of the hierarchy in an overlay
+         */
+        const val IN_OVERLAY = -1000
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
new file mode 100644
index 0000000..6e7b6bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -0,0 +1,159 @@
+package com.android.systemui.media
+
+import android.graphics.Rect
+import android.util.MathUtils
+import android.view.View
+import android.view.View.OnAttachStateChangeListener
+import android.view.ViewGroup
+import com.android.systemui.media.MediaHierarchyManager.MediaLocation
+import com.android.systemui.util.animation.MeasurementInput
+import javax.inject.Inject
+
+class MediaHost @Inject constructor(
+    private val state: MediaHostState,
+    private val mediaHierarchyManager: MediaHierarchyManager,
+    private val mediaDataManager: MediaDataManager
+) : MediaState by state {
+    lateinit var hostView: ViewGroup
+    var location: Int = -1
+        private set
+    var visibleChangedListener: ((Boolean) -> Unit)? = null
+    var visible: Boolean = false
+        private set
+
+    private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)
+
+    /**
+     * Get the current Media state. This also updates the location on screen
+     */
+    val currentState : MediaState
+        get () {
+            hostView.getLocationOnScreen(tmpLocationOnScreen)
+            var left = tmpLocationOnScreen[0] + hostView.paddingLeft
+            var top = tmpLocationOnScreen[1] + hostView.paddingTop
+            var right = tmpLocationOnScreen[0] + hostView.width - hostView.paddingRight
+            var bottom = tmpLocationOnScreen[1] + hostView.height - hostView.paddingBottom
+            // Handle cases when the width or height is 0 but it has padding. In those cases
+            // the above could return negative widths, which is wrong
+            if (right < left) {
+                left = 0
+                right = 0;
+            }
+            if (bottom < top) {
+                bottom = 0
+                top = 0;
+            }
+            state.boundsOnScreen.set(left, top, right, bottom)
+            return state
+        }
+
+    private val listener = object : MediaDataManager.Listener {
+        override fun onMediaDataLoaded(key: String, data: MediaData) {
+            updateViewVisibility()
+        }
+
+        override fun onMediaDataRemoved(key: String) {
+            updateViewVisibility()
+        }
+    }
+
+    /**
+     * Initialize this MediaObject and create a host view.
+     *
+     * @param location the location this host name has. Used to identify the host during
+     *                 transitions.
+     */
+    fun init(@MediaLocation location: Int) {
+        this.location = location;
+        hostView = mediaHierarchyManager.register(this)
+        hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(v: View?) {
+                mediaDataManager.addListener(listener)
+                updateViewVisibility()
+            }
+
+            override fun onViewDetachedFromWindow(v: View?) {
+                mediaDataManager.removeListener(listener)
+            }
+        })
+        updateViewVisibility()
+    }
+
+    private fun updateViewVisibility() {
+        if (showsOnlyActiveMedia) {
+            visible = mediaDataManager.hasActiveMedia()
+        } else {
+            visible = mediaDataManager.hasAnyMedia()
+        }
+        hostView.visibility = if (visible) View.VISIBLE else View.GONE
+        visibleChangedListener?.invoke(visible)
+    }
+
+    class MediaHostState @Inject constructor() : MediaState {
+        var measurementInput: MediaMeasurementInput? = null
+        override var expansion: Float = 0.0f
+        override var showsOnlyActiveMedia: Boolean = false
+        override val boundsOnScreen: Rect = Rect()
+
+        override fun copy() : MediaState {
+            val mediaHostState = MediaHostState()
+            mediaHostState.expansion = expansion
+            mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
+            mediaHostState.boundsOnScreen.set(boundsOnScreen)
+            mediaHostState.measurementInput = measurementInput
+            return mediaHostState
+        }
+
+        override fun interpolate(other: MediaState, amount: Float) : MediaState {
+            val result = MediaHostState()
+            result.expansion = MathUtils.lerp(expansion, other.expansion, amount)
+            val left = MathUtils.lerp(boundsOnScreen.left.toFloat(),
+                    other.boundsOnScreen.left.toFloat(), amount).toInt()
+            val top = MathUtils.lerp(boundsOnScreen.top.toFloat(),
+                    other.boundsOnScreen.top.toFloat(), amount).toInt()
+            val right = MathUtils.lerp(boundsOnScreen.right.toFloat(),
+                    other.boundsOnScreen.right.toFloat(), amount).toInt()
+            val bottom = MathUtils.lerp(boundsOnScreen.bottom.toFloat(),
+                    other.boundsOnScreen.bottom.toFloat(), amount).toInt()
+            result.boundsOnScreen.set(left, top, right, bottom)
+            result.showsOnlyActiveMedia = other.showsOnlyActiveMedia || showsOnlyActiveMedia
+            if (amount > 0.0f) {
+                if (other is MediaHostState) {
+                    result.measurementInput = other.measurementInput
+                }
+            }  else {
+                result.measurementInput
+            }
+            return  result
+        }
+
+        override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput {
+            measurementInput = MediaMeasurementInput(input, expansion)
+            return measurementInput as MediaMeasurementInput
+        }
+    }
+}
+
+interface MediaState {
+    var expansion: Float
+    var showsOnlyActiveMedia: Boolean
+    val boundsOnScreen: Rect
+    fun copy() : MediaState
+    fun interpolate(other: MediaState, amount: Float) : MediaState
+    fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput
+}
+/**
+ * The measurement input for a Media View
+ */
+data class MediaMeasurementInput(
+    private val viewInput: MeasurementInput,
+    val expansion: Float) : MeasurementInput by viewInput {
+
+    override fun sameAs(input: MeasurementInput?): Boolean {
+        if (!(input is MediaMeasurementInput)) {
+            return false
+        }
+        return width == input.width && expansion == input.expansion
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt
new file mode 100644
index 0000000..4bbf5eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt
@@ -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
+ */
+
+package com.android.systemui.media
+
+import com.android.systemui.util.animation.BaseMeasurementCache
+import com.android.systemui.util.animation.GuaranteedMeasurementCache
+import com.android.systemui.util.animation.MeasurementCache
+import com.android.systemui.util.animation.MeasurementInput
+import com.android.systemui.util.animation.MeasurementOutput
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * A class responsible creating measurement caches for media hosts which also coordinates with
+ * the view manager to obtain sizes for unknown measurement inputs.
+ */
+@Singleton
+class MediaMeasurementManager @Inject constructor(
+    private val mediaViewManager: MediaViewManager
+) {
+    private val baseCache: MeasurementCache
+
+    init {
+        baseCache = BaseMeasurementCache()
+    }
+
+    private fun provideMeasurement(input: MediaMeasurementInput) : MeasurementOutput? {
+        return mediaViewManager.obtainMeasurement(input)
+    }
+
+    /**
+     * Obtain a guaranteed measurement cache for a host view. The measurement cache makes sure that
+     * requesting any size from the cache will always return the correct value.
+     */
+    fun obtainCache(host: MediaState): GuaranteedMeasurementCache {
+        val remapper = { input: MeasurementInput ->
+            host.getMeasuringInput(input)
+        }
+        val provider = { input: MeasurementInput ->
+            provideMeasurement(input as MediaMeasurementInput)
+        }
+        return GuaranteedMeasurementCache(baseCache, remapper, provider)
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
new file mode 100644
index 0000000..9c92b0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -0,0 +1,314 @@
+package com.android.systemui.media
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.HorizontalScrollView
+import android.widget.LinearLayout
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
+import com.android.settingslib.media.LocalMediaManager
+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.statusbar.notification.VisualStabilityManager
+import com.android.systemui.util.animation.MeasurementOutput
+import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Class that is responsible for keeping the view carousel up to date.
+ * This also handles changes in state and applies them to the media carousel like the expansion.
+ */
+@Singleton
+class MediaViewManager @Inject constructor(
+    private val context: Context,
+    @Main private val foregroundExecutor: Executor,
+    @Background private val backgroundExecutor: DelayableExecutor,
+    private val localBluetoothManager: LocalBluetoothManager?,
+    private val visualStabilityManager: VisualStabilityManager,
+    private val activityStarter: ActivityStarter,
+    mediaManager: MediaDataManager
+) {
+    private var playerWidth: Int = 0
+    private var playerWidthPlusPadding: Int = 0
+    private var desiredState: MediaHost.MediaHostState? = null
+    private var currentState: MediaState? = null
+    val mediaCarousel: HorizontalScrollView
+    private val mediaContent: ViewGroup
+    private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
+    private val visualStabilityCallback = ::reorderAllPlayers
+    private var activeMediaIndex: Int = 0
+    private var scrollIntoCurrentMedia: Int = 0
+
+    private var currentlyExpanded = true
+        set(value) {
+            if (field != value) {
+                field = value
+                for (player in mediaPlayers.values) {
+                    player.setListening(field)
+                }
+            }
+        }
+    private val scrollChangedListener = object : View.OnScrollChangeListener {
+        override fun onScrollChange(
+            v: View?,
+            scrollX: Int,
+            scrollY: Int,
+            oldScrollX: Int,
+            oldScrollY: Int
+        ) {
+            if (playerWidthPlusPadding == 0) {
+                return
+            }
+            onMediaScrollingChanged(scrollX / playerWidthPlusPadding,
+                    scrollX % playerWidthPlusPadding)
+        }
+    }
+
+    init {
+        mediaCarousel = inflateMediaCarousel()
+        mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
+        mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
+        mediaManager.addListener(object : MediaDataManager.Listener {
+            override fun onMediaDataLoaded(key: String, data: MediaData) {
+                updateView(key, data)
+                updatePlayerVisibilities()
+            }
+
+            override fun onMediaDataRemoved(key: String) {
+                val removed = mediaPlayers.remove(key)
+                removed?.apply {
+                    val beforeActive = mediaContent.indexOfChild(removed.view?.player) <=
+                            activeMediaIndex
+                    mediaContent.removeView(removed.view?.player)
+                    removed.onDestroy()
+                    updateMediaPaddings()
+                    if (beforeActive) {
+                        // also update the index here since the scroll below might not always lead
+                        // to a scrolling changed
+                        activeMediaIndex = Math.max(0, activeMediaIndex - 1)
+                        mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX -
+                                playerWidthPlusPadding, 0)
+                    }
+                    updatePlayerVisibilities()
+                }
+            }
+        })
+    }
+
+    private fun inflateMediaCarousel(): HorizontalScrollView {
+        return LayoutInflater.from(context).inflate(R.layout.media_carousel,
+                UniqueObjectHostView(context), false) as HorizontalScrollView
+    }
+
+    private fun reorderAllPlayers() {
+        for (mediaPlayer in mediaPlayers.values) {
+            val view = mediaPlayer.view?.player
+            if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) {
+                mediaContent.removeView(view)
+                mediaContent.addView(view, 0)
+            }
+        }
+        updateMediaPaddings()
+        updatePlayerVisibilities()
+    }
+
+    private fun onMediaScrollingChanged(newIndex: Int, scrollInAmount: Int) {
+        val wasScrolledIn = scrollIntoCurrentMedia != 0
+        scrollIntoCurrentMedia = scrollInAmount
+        val nowScrolledIn = scrollIntoCurrentMedia != 0
+        if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
+            activeMediaIndex = newIndex
+            updatePlayerVisibilities()
+        }
+    }
+
+    private fun updatePlayerVisibilities() {
+        val scrolledIn = scrollIntoCurrentMedia != 0
+        for (i in 0 until mediaContent.childCount) {
+            val view = mediaContent.getChildAt(i)
+            val visible = (i == activeMediaIndex) || ((i == (activeMediaIndex + 1)) && scrolledIn)
+            view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
+        }
+    }
+
+    private fun updateView(key: String, data: MediaData) {
+        var existingPlayer = mediaPlayers[key]
+        if (existingPlayer == null) {
+            // Set up listener for device changes
+            // TODO: integrate with MediaTransferManager?
+            val imm = InfoMediaManager(context, data.packageName,
+                    null /* notification */, localBluetoothManager)
+            val routeManager = LocalMediaManager(context, localBluetoothManager,
+                    imm, data.packageName)
+
+            existingPlayer = MediaControlPanel(context, routeManager, foregroundExecutor,
+                    backgroundExecutor, activityStarter)
+            existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
+                    mediaContent))
+            mediaPlayers[key] = existingPlayer
+            val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT)
+            existingPlayer.view?.player?.setLayoutParams(lp)
+            existingPlayer.setListening(currentlyExpanded)
+            if (existingPlayer.isPlaying) {
+                mediaContent.addView(existingPlayer.view?.player, 0)
+            } else {
+                mediaContent.addView(existingPlayer.view?.player)
+            }
+            updatePlayerToCurrentState(existingPlayer)
+        } else if (existingPlayer.isPlaying &&
+                    mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
+            if (visualStabilityManager.isReorderingAllowed) {
+                mediaContent.removeView(existingPlayer.view?.player)
+                mediaContent.addView(existingPlayer.view?.player, 0)
+            } else {
+                visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback)
+            }
+        }
+        existingPlayer.bind(data)
+        // Resetting the progress to make sure it's taken into account for the latest
+        // motion model
+        existingPlayer.view?.player?.progress = currentState?.expansion ?: 0.0f
+        updateMediaPaddings()
+    }
+
+    private fun updatePlayerToCurrentState(existingPlayer: MediaControlPanel) {
+        if (desiredState != null && desiredState!!.measurementInput != null) {
+            // make sure the player width is set to the current state
+            existingPlayer.setPlayerWidth(playerWidth)
+        }
+    }
+
+    private fun updateMediaPaddings() {
+        val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
+        val childCount = mediaContent.childCount
+        for (i in 0 until childCount) {
+            val mediaView = mediaContent.getChildAt(i)
+            val desiredPaddingEnd = if (i == childCount - 1) 0 else padding
+            val layoutParams = mediaView.layoutParams as ViewGroup.MarginLayoutParams
+            if (layoutParams.marginEnd != desiredPaddingEnd) {
+                layoutParams.marginEnd = desiredPaddingEnd
+                mediaView.layoutParams = layoutParams
+            }
+        }
+    }
+
+    /**
+     * Set the current state of a view. This is updated often during animations and we shouldn't
+     * do anything expensive.
+     */
+    fun setCurrentState(state: MediaState) {
+        currentState = state
+        currentlyExpanded = state.expansion > 0
+        for (mediaPlayer in mediaPlayers.values) {
+            val view = mediaPlayer.view?.player
+            view?.progress = state.expansion
+        }
+    }
+
+    /**
+     * The desired location of this view has changed. We should remeasure the view to match
+     * the new bounds and kick off bounds animations if necessary.
+     * If an animation is happening, an animation is kicked of externally, which sets a new
+     * current state until we reach the targetState.
+     *
+     * @param desiredState the target state we're transitioning to
+     * @param animate should this be animated
+     */
+    fun onDesiredLocationChanged(
+        desiredState: MediaState?,
+        animate: Boolean,
+        duration: Long,
+        startDelay: Long
+    ) {
+        if (desiredState is MediaHost.MediaHostState) {
+            // This is a hosting view, let's remeasure our players
+            this.desiredState = desiredState
+            val width = desiredState.boundsOnScreen.width()
+            if (playerWidth != width) {
+                setPlayerWidth(width)
+                for (mediaPlayer in mediaPlayers.values) {
+                    if (animate && mediaPlayer.view?.player?.visibility == View.VISIBLE) {
+                        mediaPlayer.animatePendingSizeChange(duration, startDelay)
+                    }
+                }
+                val widthSpec = desiredState.measurementInput?.widthMeasureSpec ?: 0
+                val heightSpec = desiredState.measurementInput?.heightMeasureSpec ?: 0
+                var left = 0
+                for (i in 0 until mediaContent.childCount) {
+                    val view = mediaContent.getChildAt(i)
+                    view.measure(widthSpec, heightSpec)
+                    view.layout(left, 0, left + width, view.measuredHeight)
+                    left = left + playerWidthPlusPadding
+                }
+            }
+        }
+    }
+
+    fun setPlayerWidth(width: Int) {
+        if (width != playerWidth) {
+            playerWidth = width
+            playerWidthPlusPadding = playerWidth + context.resources.getDimensionPixelSize(
+                    R.dimen.qs_media_padding)
+            for (mediaPlayer in mediaPlayers.values) {
+                mediaPlayer.setPlayerWidth(width)
+            }
+            // The player width has changed, let's update the scroll position to make sure
+            // it's still at the same place
+            var newScroll = activeMediaIndex * playerWidthPlusPadding
+            if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
+                newScroll += playerWidthPlusPadding
+                - (scrollIntoCurrentMedia - playerWidthPlusPadding)
+            } else {
+                newScroll += scrollIntoCurrentMedia
+            }
+            mediaCarousel.scrollX = newScroll
+        }
+    }
+
+    /**
+     * Get a measurement for the given input state. This measures the first player and returns
+     * its bounds as if it were measured with the given measurement dimensions
+     */
+    fun obtainMeasurement(input: MediaMeasurementInput): MeasurementOutput? {
+        val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null
+        var result: MeasurementOutput? = null
+        firstPlayer.view?.player?.let {
+            // Let's measure the size of the first player and return its height
+            val previousProgress = it.progress
+            val previousRight = it.right
+            val previousBottom = it.bottom
+            it.progress = input.expansion
+            firstPlayer.measure(input)
+            // Relayouting is necessary in motionlayout to obtain its size properly ....
+            it.layout(0, 0, it.measuredWidth, it.measuredHeight)
+            val result = MeasurementOutput(it.measuredWidth, it.measuredHeight)
+            it.progress = previousProgress
+            if (desiredState != null) {
+                // remeasure it to the old size again!
+                firstPlayer.measure(desiredState!!.measurementInput)
+                it.layout(0, 0, previousRight, previousBottom)
+            }
+        }
+        return result
+    }
+
+    fun onViewReattached() {
+        if (desiredState is MediaHost.MediaHostState) {
+            // HACK: MotionLayout doesn't always properly reevalate the state, let's kick of
+            // a measure to force it.
+            val widthSpec = desiredState!!.measurementInput?.widthMeasureSpec ?: 0
+            val heightSpec = desiredState!!.measurementInput?.heightMeasureSpec ?: 0
+            for (mediaPlayer in mediaPlayers.values) {
+                mediaPlayer.view?.player?.measure(widthSpec, heightSpec)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
new file mode 100644
index 0000000..571e18d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
+
+import androidx.constraintlayout.motion.widget.MotionLayout
+
+import com.android.systemui.R
+
+/**
+ * ViewHolder for a media player.
+ */
+class PlayerViewHolder private constructor(itemView: View) {
+
+    val player = itemView as MotionLayout
+    val background = itemView.requireViewById<View>(R.id.media_background)
+
+    // Player information
+    val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
+    val appName = itemView.requireViewById<TextView>(R.id.app_name)
+    val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
+    val titleText = itemView.requireViewById<TextView>(R.id.header_title)
+    val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
+
+    // Output switcher
+    val seamless = itemView.findViewById<ViewGroup>(R.id.media_seamless)
+    val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
+    val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
+
+    // Seek bar
+    val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
+    val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
+    val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
+
+    // Action Buttons
+    val action0 = itemView.requireViewById<ImageButton>(R.id.action0)
+    val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
+    val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
+    val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
+    val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
+
+    fun getAction(id: Int): ImageButton {
+        return when (id) {
+            R.id.action0 -> action0
+            R.id.action1 -> action1
+            R.id.action2 -> action2
+            R.id.action3 -> action3
+            R.id.action4 -> action4
+            else -> {
+                throw IllegalArgumentException()
+            }
+        }
+    }
+
+    // Settings screen
+    val options = itemView.requireViewById<View>(R.id.qs_media_controls_options)
+
+    companion object {
+        /**
+         * Creates a PlayerViewHolder.
+         *
+         * @param inflater LayoutInflater to use to inflate the layout.
+         * @param parent Parent of inflated view.
+         */
+        @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
+            val v = inflater.inflate(R.layout.qs_media_panel, parent, false)
+            return PlayerViewHolder(v)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt
new file mode 100644
index 0000000..8efc954
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.media
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.HorizontalScrollView
+
+/**
+ * A Horizontal scrollview that doesn't limit itself to the childs bounds. This is useful
+ * when only measuring children but not the parent, when trying to apply a new scroll position
+ */
+class UnboundHorizontalScrollView @JvmOverloads constructor(
+    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
+    : HorizontalScrollView(context, attrs, defStyleAttr) {
+
+    /**
+     * Allow all scrolls to go through, use base implementation
+     */
+    override fun scrollTo(x: Int, y: Int) {
+        if (mScrollX != x || mScrollY != y) {
+            val oldX: Int = mScrollX
+            val oldY: Int = mScrollY
+            mScrollX = x
+            mScrollY = y
+            invalidateParentCaches()
+            onScrollChanged(mScrollX, mScrollY, oldX, oldY)
+            if (!awakenScrollBars()) {
+                postInvalidateOnAnimation()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 8d6ce47..7e2efc0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -552,6 +552,9 @@
                     ? null : destinationBounds;
             // As for the final windowing mode, simply reset it to undefined.
             wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+            if (mSplitDivider != null && direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN) {
+                wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */);
+            }
         } else {
             taskBounds = destinationBounds;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index c5ae3ab..40d317c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -117,7 +117,7 @@
             it.tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
         }
 
-        val height = twoLineHeight
+        val height = twoLineHeight + paddingBottom + paddingTop
         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a0ea7fa..ce00229 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -269,14 +269,6 @@
             count++;
         }
 
-
-        if (Utils.useQsMediaPlayer(mQsPanel.getContext())) {
-            View qsMediaView = mQsPanel.getMediaPanel();
-            View qqsMediaView = mQuickQsPanel.getMediaPlayer().getView();
-            translationXBuilder.addFloat(qsMediaView, "alpha", 0, 1);
-            translationXBuilder.addFloat(qqsMediaView, "alpha", 1, 0);
-        }
-
         if (mAllowFancy) {
             // Make brightness appear static position and alpha in through second half.
             View brightness = mQsPanel.getBrightnessView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index be8a8fd..6b0775f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -42,7 +42,7 @@
     private QuickStatusBarHeader mHeader;
     private float mQsExpansion;
     private QSCustomizer mQSCustomizer;
-    private View mQSFooter;
+    private View mDragHandle;
 
     private View mBackground;
     private View mBackgroundGradient;
@@ -62,7 +62,7 @@
         mQSDetail = findViewById(R.id.qs_detail);
         mHeader = findViewById(R.id.header);
         mQSCustomizer = findViewById(R.id.qs_customize);
-        mQSFooter = findViewById(R.id.qs_footer);
+        mDragHandle = findViewById(R.id.qs_drag_handle_view);
         mBackground = findViewById(R.id.quick_settings_background);
         mStatusBarBackground = findViewById(R.id.quick_settings_status_bar_background);
         mBackgroundGradient = findViewById(R.id.quick_settings_gradient_view);
@@ -167,8 +167,8 @@
         int height = calculateContainerHeight();
         setBottom(getTop() + height);
         mQSDetail.setBottom(getTop() + height);
-        // Pin QS Footer to the bottom of the panel.
-        mQSFooter.setTranslationY(height - mQSFooter.getHeight());
+        // Pin the drag handle to the bottom of the panel.
+        mDragHandle.setTranslationY(height - mDragHandle.getHeight());
         mBackground.setTop(mQSPanel.getTop());
         mBackground.setBottom(height);
     }
@@ -192,13 +192,13 @@
 
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
+        mDragHandle.setAlpha(1.0f - expansion);
         updateExpansion();
     }
 
     private void setMargins() {
         setMargins(mQSDetail);
         setMargins(mBackground);
-        setMargins(mQSFooter);
         mQSPanel.setMargins(mSideMargins);
         mHeader.setMargins(mSideMargins);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 5de6d1c..fc8e36f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -98,7 +98,6 @@
     private TouchAnimator mSettingsCogAnimator;
 
     private View mActionsContainer;
-    private View mDragHandle;
 
     private OnClickListener mExpandClickListener;
 
@@ -146,7 +145,6 @@
         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
 
-        mDragHandle = findViewById(R.id.qs_drag_handle_view);
         mActionsContainer = findViewById(R.id.qs_footer_actions_container);
         mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
 
@@ -219,7 +217,6 @@
         return new TouchAnimator.Builder()
                 .addFloat(mActionsContainer, "alpha", 0, 1)
                 .addFloat(mEditContainer, "alpha", 0, 1)
-                .addFloat(mDragHandle, "alpha", 1, 0, 0)
                 .addFloat(mPageIndicator, "alpha", 0, 1)
                 .setStartDelay(0.15f)
                 .build();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 5b09267..865fd07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -37,6 +37,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.R.id;
+import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.customize.QSCustomizer;
@@ -47,6 +48,7 @@
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
 import com.android.systemui.util.InjectionInflationController;
 import com.android.systemui.util.LifecycleFragment;
+import com.android.systemui.util.Utils;
 
 import javax.inject.Inject;
 
@@ -91,6 +93,7 @@
      */
     private int mState;
     private QSContainerImplController mQSContainerImplController;
+    private int[] mTmpLocation = new int[2];
 
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -377,8 +380,7 @@
         mLastKeyguardAndExpanded = onKeyguardAndExpanded;
 
         boolean fullyExpanded = expansion == 1;
-        int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
-                + mFooter.getHeight();
+        int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom();
         float panelTranslationY = translationScaleY * heightDiff;
 
         // Let the views animate their contents correctly by giving them the necessary context.
@@ -404,6 +406,32 @@
         if (mQSAnimator != null) {
             mQSAnimator.setPosition(expansion);
         }
+        updateMediaPositions();
+    }
+
+    private void updateMediaPositions() {
+        if (Utils.useQsMediaPlayer(getContext())) {
+            mContainer.getLocationOnScreen(mTmpLocation);
+            float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight();
+            pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost());
+            pinToBottom(absoluteBottomPosition - mHeader.getPaddingBottom(),
+                    mHeader.getHeaderQsPanel().getMediaHost());
+        }
+    }
+
+    private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost) {
+        View hostView = mediaHost.getHostView();
+        if (mLastQSExpansion > 0) {
+            ViewGroup.MarginLayoutParams params =
+                    (ViewGroup.MarginLayoutParams) hostView.getLayoutParams();
+            float targetPosition = absoluteBottomPosition - params.bottomMargin
+                    - hostView.getHeight();
+            float currentPosition = mediaHost.getCurrentState().getBoundsOnScreen().top
+                    - hostView.getTranslationY();
+            hostView.setTranslationY(targetPosition - currentPosition);
+        } else {
+            hostView.setTranslationY(0);
+        }
     }
 
     private boolean headerWillBeAnimating() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
deleted file mode 100644
index 174441b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.MediaDescription;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.settingslib.media.LocalMediaManager;
-import com.android.systemui.R;
-import com.android.systemui.media.IlluminationDrawable;
-import com.android.systemui.media.MediaControlPanel;
-import com.android.systemui.media.SeekBarObserver;
-import com.android.systemui.media.SeekBarViewModel;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import java.util.concurrent.Executor;
-
-/**
- * Single media player for carousel in QSPanel
- */
-public class QSMediaPlayer extends MediaControlPanel {
-
-    private static final String TAG = "QSMediaPlayer";
-
-    // Button IDs for QS controls
-    static final int[] QS_ACTION_IDS = {
-            R.id.action0,
-            R.id.action1,
-            R.id.action2,
-            R.id.action3,
-            R.id.action4
-    };
-
-    private final QSPanel mParent;
-    private final Executor mForegroundExecutor;
-    private final DelayableExecutor mBackgroundExecutor;
-    private final SeekBarViewModel mSeekBarViewModel;
-    private final SeekBarObserver mSeekBarObserver;
-    private String mPackageName;
-
-    /**
-     * Initialize quick shade version of player
-     * @param context
-     * @param parent
-     * @param routeManager Provides information about device
-     * @param foregroundExecutor
-     * @param backgroundExecutor
-     * @param activityStarter
-     */
-    public QSMediaPlayer(Context context, ViewGroup parent, LocalMediaManager routeManager,
-            Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
-            ActivityStarter activityStarter) {
-        super(context, parent, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
-                foregroundExecutor, backgroundExecutor, activityStarter);
-        mParent = (QSPanel) parent;
-        mForegroundExecutor = foregroundExecutor;
-        mBackgroundExecutor = backgroundExecutor;
-        mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
-        mSeekBarObserver = new SeekBarObserver(getView());
-        // Can't use the viewAttachLifecycle of media player because remove/add is used to adjust
-        // priority of players. As soon as it is removed, the lifecycle will end and the seek bar
-        // will stop updating. So, use the lifecycle of the parent instead.
-        mSeekBarViewModel.getProgress().observe(viewAttachLifecycle(parent), mSeekBarObserver);
-        SeekBar bar = getView().findViewById(R.id.media_progress_bar);
-        bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
-        bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
-    }
-
-    /**
-     * Add a media panel view based on a media description. Used for resumption
-     * @param description
-     * @param iconColor
-     * @param bgColor
-     * @param contentIntent
-     * @param pkgName
-     */
-    public void setMediaSession(MediaSession.Token token, MediaDescription description,
-            int iconColor, int bgColor, PendingIntent contentIntent, String pkgName) {
-        mPackageName = pkgName;
-        PackageManager pm = getContext().getPackageManager();
-        Drawable icon = null;
-        CharSequence appName = pkgName.substring(pkgName.lastIndexOf("."));
-        try {
-            icon = pm.getApplicationIcon(pkgName);
-            appName = pm.getApplicationLabel(pm.getApplicationInfo(pkgName, 0));
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Error getting package information", e);
-        }
-
-        // Set what we can normally
-        super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
-                appName.toString(), null);
-
-        // Then add info from MediaDescription
-        ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
-        if (albumView != null) {
-            // Resize art in a background thread
-            mBackgroundExecutor.execute(() -> processAlbumArt(description, albumView));
-        }
-
-        // Song name
-        TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
-        CharSequence songName = description.getTitle();
-        titleText.setText(songName);
-        titleText.setTextColor(iconColor);
-
-        // Artist name (not in mini player)
-        TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
-        if (artistText != null) {
-            CharSequence artistName = description.getSubtitle();
-            artistText.setText(artistName);
-            artistText.setTextColor(iconColor);
-        }
-
-        initLongPressMenu(iconColor);
-
-        // Set buttons to resume state
-        resetButtons();
-    }
-
-    /**
-     * Update media panel view for the given media session
-     * @param token token for this media session
-     * @param icon app notification icon
-     * @param largeIcon notification's largeIcon, used as a fallback for album art
-     * @param iconColor foreground color (for text, icons)
-     * @param bgColor background color
-     * @param actionsContainer a LinearLayout containing the media action buttons
-     * @param contentIntent Intent to send when user taps on player
-     * @param appName Application title
-     * @param key original notification's key
-     */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
-            int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
-            String appName, String key) {
-
-        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
-                key);
-
-        // Media controls
-        if (actionsContainer != null) {
-            LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
-            int i = 0;
-            for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
-                final ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
-                ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
-                if (thatBtn == null || thatBtn.getDrawable() == null
-                        || thatBtn.getVisibility() != View.VISIBLE) {
-                    thisBtn.setVisibility(View.GONE);
-                    continue;
-                }
-
-                if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
-                    ((IlluminationDrawable) mMediaNotifView.getBackground())
-                            .setupTouch(thisBtn, mMediaNotifView);
-                }
-
-                Drawable thatIcon = thatBtn.getDrawable();
-                thisBtn.setImageDrawable(thatIcon.mutate());
-                thisBtn.setVisibility(View.VISIBLE);
-                thisBtn.setOnClickListener(v -> {
-                    Log.d(TAG, "clicking on other button");
-                    thatBtn.performClick();
-                });
-            }
-
-            // Hide any unused buttons
-            for (; i < QS_ACTION_IDS.length; i++) {
-                ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
-                thisBtn.setVisibility(View.GONE);
-            }
-        }
-
-        // Seek Bar
-        final MediaController controller = new MediaController(getContext(), token);
-        mBackgroundExecutor.execute(
-                () -> mSeekBarViewModel.updateController(controller, iconColor));
-
-        initLongPressMenu(iconColor);
-    }
-
-    private void initLongPressMenu(int iconColor) {
-        // Set up long press menu
-        View guts = mMediaNotifView.findViewById(R.id.media_guts);
-        View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
-        options.setMinimumHeight(guts.getHeight());
-
-        View clearView = options.findViewById(R.id.remove);
-        clearView.setOnClickListener(b -> {
-            removePlayer();
-        });
-        ImageView removeIcon = options.findViewById(R.id.remove_icon);
-        removeIcon.setImageTintList(ColorStateList.valueOf(iconColor));
-        TextView removeText = options.findViewById(R.id.remove_text);
-        removeText.setTextColor(iconColor);
-
-        TextView cancelView = options.findViewById(R.id.cancel);
-        cancelView.setTextColor(iconColor);
-        cancelView.setOnClickListener(b -> {
-            options.setVisibility(View.GONE);
-            guts.setVisibility(View.VISIBLE);
-        });
-        // ... but don't enable it yet, and make sure is reset when the session is updated
-        mMediaNotifView.setOnLongClickListener(null);
-        options.setVisibility(View.GONE);
-        guts.setVisibility(View.VISIBLE);
-    }
-
-    @Override
-    protected void resetButtons() {
-        super.resetButtons();
-        mSeekBarViewModel.clearController();
-        View guts = mMediaNotifView.findViewById(R.id.media_guts);
-        View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
-
-        mMediaNotifView.setOnLongClickListener(v -> {
-            // Replace player view with close/cancel view
-            guts.setVisibility(View.GONE);
-            options.setVisibility(View.VISIBLE);
-            return true; // consumed click
-        });
-    }
-
-    /**
-     * Sets the listening state of the player.
-     *
-     * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
-     * unnecessary work when the QS panel is closed.
-     *
-     * @param listening True when player should be active. Otherwise, false.
-     */
-    public void setListening(boolean listening) {
-        mSeekBarViewModel.setListening(listening);
-    }
-
-    @Override
-    public void removePlayer() {
-        Log.d(TAG, "removing player from parent: " + mParent);
-        // Ensure this happens on the main thread (could happen in QSMediaBrowser callback)
-        mForegroundExecutor.execute(() -> mParent.removeMediaPlayer(QSMediaPlayer.this));
-    }
-
-    @Override
-    public String getMediaPlayerPackage() {
-        if (getController() == null) {
-            return mPackageName;
-        }
-        return super.getMediaPlayerPackage();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 0ac4d15..009ebd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -32,23 +32,19 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
-import android.media.session.MediaSession;
 import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.service.notification.StatusBarNotification;
 import android.service.quicksettings.Tile;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.MetricsLogger;
@@ -56,17 +52,14 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.settingslib.Utils;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.media.InfoMediaManager;
-import com.android.settingslib.media.LocalMediaManager;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaControlPanel;
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile;
@@ -77,20 +70,16 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSliderView;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
@@ -108,21 +97,13 @@
     protected final Context mContext;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
     private final BroadcastDispatcher mBroadcastDispatcher;
+    protected final MediaHost mMediaHost;
     private String mCachedSpecs = "";
     protected final View mBrightnessView;
     private final H mHandler = new H();
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private final QSTileRevealController mQsTileRevealController;
 
-    private final LinearLayout mMediaCarousel;
-    private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
-    private final LocalBluetoothManager mLocalBluetoothManager;
-    private final Executor mForegroundExecutor;
-    private final DelayableExecutor mBackgroundExecutor;
-    private boolean mUpdateCarousel = false;
-    private ActivityStarter mActivityStarter;
-    private NotificationEntryManager mNotificationEntryManager;
-
     protected boolean mExpanded;
     protected boolean mListening;
 
@@ -158,15 +139,6 @@
         }
     };
 
-    private final NotificationEntryListener mNotificationEntryListener =
-            new NotificationEntryListener() {
-        @Override
-        public void onEntryRemoved(NotificationEntry entry, NotificationVisibility visibility,
-                boolean removedByUser, int reason) {
-            checkToRemoveMediaNotification(entry);
-        }
-    };
-
     @Inject
     public QSPanel(
             @Named(VIEW_CONTEXT) Context context,
@@ -174,23 +146,15 @@
             DumpManager dumpManager,
             BroadcastDispatcher broadcastDispatcher,
             QSLogger qsLogger,
-            @Main Executor foregroundExecutor,
-            @Background DelayableExecutor backgroundExecutor,
-            @Nullable LocalBluetoothManager localBluetoothManager,
-            ActivityStarter activityStarter,
-            NotificationEntryManager entryManager,
+            MediaHost mediaHost,
             UiEventLogger uiEventLogger
     ) {
         super(context, attrs);
+        mMediaHost = mediaHost;
         mContext = context;
         mQSLogger = qsLogger;
         mDumpManager = dumpManager;
-        mForegroundExecutor = foregroundExecutor;
-        mBackgroundExecutor = backgroundExecutor;
-        mLocalBluetoothManager = localBluetoothManager;
         mBroadcastDispatcher = broadcastDispatcher;
-        mActivityStarter = activityStarter;
-        mNotificationEntryManager = entryManager;
         mUiEventLogger = uiEventLogger;
 
         setOrientation(VERTICAL);
@@ -210,16 +174,6 @@
 
         addDivider();
 
-        // Add media carousel
-        if (useQsMediaPlayer(context)) {
-            HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from(
-                    mContext).inflate(R.layout.media_carousel, this, false);
-            mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel);
-            addView(mediaScrollView, 0);
-        } else {
-            mMediaCarousel = null;
-        }
-
         mFooter = new QSSecurityFooter(this, context);
         addView(mFooter.getView());
 
@@ -230,145 +184,39 @@
     }
 
     @Override
-    public void onVisibilityAggregated(boolean isVisible) {
-        super.onVisibilityAggregated(isVisible);
-        if (!isVisible && mUpdateCarousel) {
-            for (QSMediaPlayer player : mMediaPlayers) {
-                if (player.isPlaying()) {
-                    LayoutParams lp = (LayoutParams) player.getView().getLayoutParams();
-                    mMediaCarousel.removeView(player.getView());
-                    mMediaCarousel.addView(player.getView(), 0, lp);
-                    ((HorizontalScrollView) mMediaCarousel.getParent()).fullScroll(View.FOCUS_LEFT);
-                    mUpdateCarousel = false;
-                    break;
-                }
-            }
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        // Add media carousel at the end
+        if (useQsMediaPlayer(getContext())) {
+            addMediaHostView();
         }
     }
 
-    /**
-     * Add or update a player for the associated media session
-     * @param token
-     * @param icon
-     * @param largeIcon
-     * @param iconColor
-     * @param bgColor
-     * @param actionsContainer
-     * @param notif
-     * @param key
-     */
-    public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
-            int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
-            String key) {
-        if (!useQsMediaPlayer(mContext)) {
-            // Shouldn't happen, but just in case
-            Log.e(TAG, "Tried to add media session without player!");
-            return;
-        }
-        if (token == null) {
-            Log.e(TAG, "Media session token was null!");
-            return;
-        }
-
-        String packageName = notif.getPackageName();
-        QSMediaPlayer player = findMediaPlayer(packageName, token, key);
-
-        int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
-        int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
-        LayoutParams lp = new LayoutParams(playerWidth, ViewGroup.LayoutParams.MATCH_PARENT);
-        lp.setMarginStart(padding);
-        lp.setMarginEnd(padding);
-
-        if (player == null) {
-            Log.d(TAG, "creating new player for " + packageName);
-            // Set up listener for device changes
-            // TODO: integrate with MediaTransferManager?
-            InfoMediaManager imm = new InfoMediaManager(mContext, notif.getPackageName(),
-                    notif.getNotification(), mLocalBluetoothManager);
-            LocalMediaManager routeManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
-                    imm, notif.getPackageName());
-
-            player = new QSMediaPlayer(mContext, this, routeManager, mForegroundExecutor,
-                    mBackgroundExecutor, mActivityStarter);
-            player.setListening(mListening);
-            if (player.isPlaying()) {
-                mMediaCarousel.addView(player.getView(), 0, lp); // add in front
-            } else {
-                mMediaCarousel.addView(player.getView(), lp); // add at end
-            }
-            mMediaPlayers.add(player);
-        } else if (player.isPlaying()) {
-            mUpdateCarousel = true;
-        }
-
-        Log.d(TAG, "setting player session");
-        String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
-                .loadHeaderAppName();
-        player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
-                notif.getNotification().contentIntent, appName, key);
-
-        if (mMediaPlayers.size() > 0) {
-            ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
-        }
-    }
-
-    /**
-     * Check for an existing media player using the given information
-     * @param packageName
-     * @param token
-     * @param key
-     * @return a player, or null if no match found
-     */
-    private QSMediaPlayer findMediaPlayer(String packageName, MediaSession.Token token,
-            String key) {
-        for (QSMediaPlayer player : mMediaPlayers) {
-            if (player.getKey() == null || key == null) {
-                // No notification key = loaded via mediabrowser, so just match on package
-                if (packageName.equals(player.getMediaPlayerPackage())) {
-                    Log.d(TAG, "Found matching resume player by package: " + packageName);
-                    return player;
-                }
-            } else if (player.getMediaSessionToken().equals(token)) {
-                Log.d(TAG, "Found matching player by token " + packageName);
-                return player;
-            } else if (packageName.equals(player.getMediaPlayerPackage())
-                    && key.equals(player.getKey())) {
-                // Also match if it's the same package and notification key
-                Log.d(TAG, "Found matching player by package " + packageName + ", " + key);
-                return player;
-            }
-        }
-        return null;
-    }
-
-    protected View getMediaPanel() {
-        return mMediaCarousel;
-    }
-
-    /**
-     * Remove the media player from the carousel
-     * @param player Player to remove
-     * @return true if removed, false if player was not found
-     */
-    protected boolean removeMediaPlayer(QSMediaPlayer player) {
-        // Remove from list
-        if (!mMediaPlayers.remove(player)) {
-            return false;
-        }
-
-        // Check if we need to collapse the carousel now
-        mMediaCarousel.removeView(player.getView());
-        if (mMediaPlayers.size() == 0) {
-            ((View) mMediaCarousel.getParent()).setVisibility(View.GONE);
-        }
-        return true;
+    protected void addMediaHostView() {
+        mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
+        mMediaHost.setExpansion(1.0f);
+        mMediaHost.setShowsOnlyActiveMedia(false);
+        ViewGroup hostView = mMediaHost.getHostView();
+        addView(hostView);
+        int sidePaddings = getResources().getDimensionPixelSize(
+                R.dimen.quick_settings_side_margins);
+        int bottomPadding = getResources().getDimensionPixelSize(
+                R.dimen.quick_settings_expanded_bottom_margin);
+        MarginLayoutParams layoutParams = (MarginLayoutParams) hostView.getLayoutParams();
+        layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        layoutParams.bottomMargin = bottomPadding;
+        hostView.setLayoutParams(layoutParams);
+        hostView.setPadding(sidePaddings, hostView.getPaddingTop(), sidePaddings,
+                hostView.getPaddingBottom());
     }
 
     private final QSMediaBrowser.Callback mMediaBrowserCallback = new QSMediaBrowser.Callback() {
         @Override
         public void addTrack(MediaDescription desc, ComponentName component,
                 QSMediaBrowser browser) {
-            if (component == null) {
+            // TODO: Fix Resumption b/156104922
+/*            if (component == null) {
                 Log.e(TAG, "Component cannot be null");
                 return;
             }
@@ -404,7 +252,7 @@
             int iconColor = Color.DKGRAY;
             int bgColor = Color.LTGRAY;
             player.setMediaSession(token, desc, iconColor, bgColor, browser.getAppIntent(),
-                    pkgName);
+                    pkgName);*/
         }
     };
 
@@ -441,27 +289,6 @@
         mHasLoadedMediaControls = true;
     }
 
-    private void checkToRemoveMediaNotification(NotificationEntry entry) {
-        if (!useQsMediaPlayer(mContext)) {
-            return;
-        }
-
-        if (!entry.isMediaNotification()) {
-            return;
-        }
-
-        // If this entry corresponds to an existing set of controls, clear the controls
-        // This will handle apps that use an action to clear their notification
-        for (QSMediaPlayer p : mMediaPlayers) {
-            if (p.getKey() != null && p.getKey().equals(entry.getKey())) {
-                Log.d(TAG, "Clearing controls since notification removed " + entry.getKey());
-                p.clearControls();
-                return;
-            }
-        }
-        Log.d(TAG, "Media notification removed but no player found " + entry.getKey());
-    }
-
     protected void addDivider() {
         mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
         mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
@@ -482,7 +309,11 @@
         int numChildren = getChildCount();
         for (int i = 0; i < numChildren; i++) {
             View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE) height += child.getMeasuredHeight();
+            if (child.getVisibility() != View.GONE) {
+                height += child.getMeasuredHeight();
+                MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+                height += layoutParams.topMargin + layoutParams.bottomMargin;
+            }
         }
         setMeasuredDimension(getMeasuredWidth(), height);
     }
@@ -528,7 +359,6 @@
                 loadMediaResumptionControls();
             }
         }
-        mNotificationEntryManager.addNotificationEntryListener(mNotificationEntryListener);
     }
 
     @Override
@@ -545,7 +375,6 @@
         }
         mDumpManager.unregisterDumpable(getDumpableTag());
         mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
-        mNotificationEntryManager.removeNotificationEntryListener(mNotificationEntryListener);
         super.onDetachedFromWindow();
     }
 
@@ -719,7 +548,6 @@
 
     public void setListening(boolean listening) {
         if (mListening == listening) return;
-        mListening = listening;
         if (mTileLayout != null) {
             mQSLogger.logAllTilesChangeListening(listening, getDumpableTag(), mCachedSpecs);
             mTileLayout.setListening(listening);
@@ -727,9 +555,6 @@
         if (mListening) {
             refreshAllTiles();
         }
-        for (QSMediaPlayer player : mMediaPlayers) {
-            player.setListening(mListening);
-        }
     }
 
     private String getTilesSpecs() {
@@ -1030,6 +855,10 @@
         }
     }
 
+    public MediaHost getMediaHost() {
+        return mMediaHost;
+    }
+
     private class H extends Handler {
         private static final int SHOW_DETAIL = 1;
         private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
deleted file mode 100644
index 5cb75e6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.media.IlluminationDrawable;
-import com.android.systemui.media.MediaControlPanel;
-import com.android.systemui.plugins.ActivityStarter;
-
-import java.util.concurrent.Executor;
-
-/**
- * QQS mini media player
- */
-public class QuickQSMediaPlayer extends MediaControlPanel {
-
-    private static final String TAG = "QQSMediaPlayer";
-
-    // Button IDs for QS controls
-    private static final int[] QQS_ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
-
-    /**
-     * Initialize mini media player for QQS
-     * @param context
-     * @param parent
-     * @param foregroundExecutor
-     * @param backgroundExecutor
-     * @param activityStarter
-     */
-    public QuickQSMediaPlayer(Context context, ViewGroup parent, Executor foregroundExecutor,
-            Executor backgroundExecutor, ActivityStarter activityStarter) {
-        super(context, parent, null, R.layout.qqs_media_panel, QQS_ACTION_IDS,
-                foregroundExecutor, backgroundExecutor, activityStarter);
-    }
-
-    /**
-     * Update media panel view for the given media session
-     * @param token token for this media session
-     * @param icon app notification icon
-     * @param largeIcon notification's largeIcon, used as a fallback for album art
-     * @param iconColor foreground color (for text, icons)
-     * @param bgColor background color
-     * @param actionsContainer a LinearLayout containing the media action buttons
-     * @param actionsToShow indices of which actions to display in the mini player
-     *                      (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
-     * @param contentIntent Intent to send when user taps on the view
-     * @param key original notification's key
-     */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
-            int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
-            PendingIntent contentIntent, String key) {
-        // Only update if this is a different session and currently playing
-        String oldPackage = "";
-        if (getController() != null) {
-            oldPackage = getController().getPackageName();
-        }
-        MediaController controller = new MediaController(getContext(), token);
-        MediaSession.Token currentToken = getMediaSessionToken();
-        boolean samePlayer = currentToken != null
-                && currentToken.equals(token)
-                && oldPackage.equals(controller.getPackageName());
-        if (getController() != null && !samePlayer && !isPlaying(controller)) {
-            return;
-        }
-
-        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
-
-        LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
-        int i = 0;
-        if (actionsToShow != null) {
-            int maxButtons = Math.min(actionsToShow.length, parentActionsLayout.getChildCount());
-            maxButtons = Math.min(maxButtons, QQS_ACTION_IDS.length);
-            for (; i < maxButtons; i++) {
-                ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
-                int thatId = NOTIF_ACTION_IDS[actionsToShow[i]];
-                ImageButton thatBtn = parentActionsLayout.findViewById(thatId);
-                if (thatBtn == null || thatBtn.getDrawable() == null
-                        || thatBtn.getVisibility() != View.VISIBLE) {
-                    thisBtn.setVisibility(View.GONE);
-                    continue;
-                }
-
-                if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
-                    ((IlluminationDrawable) mMediaNotifView.getBackground())
-                            .setupTouch(thisBtn, mMediaNotifView);
-                }
-
-                Drawable thatIcon = thatBtn.getDrawable();
-                thisBtn.setImageDrawable(thatIcon.mutate());
-                thisBtn.setVisibility(View.VISIBLE);
-                thisBtn.setOnClickListener(v -> {
-                    thatBtn.performClick();
-                });
-            }
-        }
-
-        // Hide any unused buttons
-        for (; i < QQS_ACTION_IDS.length; i++) {
-            ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
-            thisBtn.setVisibility(View.GONE);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 6683a1c..dfd385d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -18,38 +18,34 @@
 
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.Utils;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -67,16 +63,17 @@
     private boolean mDisabledByPolicy;
     private int mMaxTiles;
     protected QSPanel mFullPanel;
-    private QuickQSMediaPlayer mMediaPlayer;
     /** Whether or not the QS media player feature is enabled. */
     private boolean mUsingMediaPlayer;
     /** Whether or not the QuickQSPanel currently contains a media player. */
-    private boolean mHasMediaPlayer;
+    private boolean mShowHorizontalTileLayout;
     private LinearLayout mHorizontalLinearLayout;
 
     // Only used with media
-    private QSTileLayout mMediaTileLayout;
+    private QSTileLayout mHorizontalTileLayout;
     private QSTileLayout mRegularTileLayout;
+    private int mLastOrientation = -1;
+    private int mMediaBottomMargin;
 
     @Inject
     public QuickQSPanel(
@@ -85,16 +82,11 @@
             DumpManager dumpManager,
             BroadcastDispatcher broadcastDispatcher,
             QSLogger qsLogger,
-            @Main Executor foregroundExecutor,
-            @Background DelayableExecutor backgroundExecutor,
-            @Nullable LocalBluetoothManager localBluetoothManager,
-            ActivityStarter activityStarter,
-            NotificationEntryManager entryManager,
+            MediaHost mediaHost,
             UiEventLogger uiEventLogger
     ) {
-        super(context, attrs, dumpManager, broadcastDispatcher, qsLogger,
-                foregroundExecutor, backgroundExecutor, localBluetoothManager, activityStarter,
-                entryManager, uiEventLogger);
+        super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost,
+                uiEventLogger);
         if (mFooter != null) {
             removeView(mFooter.getView());
         }
@@ -104,6 +96,8 @@
             }
             removeView((View) mTileLayout);
         }
+        mMediaBottomMargin = getResources().getDimensionPixelSize(
+                R.dimen.quick_settings_media_extra_bottom_margin);
 
         mUsingMediaPlayer = Utils.useQsMediaPlayer(context);
         if (mUsingMediaPlayer) {
@@ -112,40 +106,95 @@
             mHorizontalLinearLayout.setClipChildren(false);
             mHorizontalLinearLayout.setClipToPadding(false);
 
-            int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
-            mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout,
-                    foregroundExecutor, backgroundExecutor, activityStarter);
-            LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
-            lp2.setMarginEnd(marginSize);
-            lp2.setMarginStart(0);
-            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
-
-            mTileLayout = new DoubleLineTileLayout(context, mUiEventLogger);
-            mMediaTileLayout = mTileLayout;
+            DoubleLineTileLayout horizontalTileLayout = new DoubleLineTileLayout(context,
+                    mUiEventLogger);
+            horizontalTileLayout.setPaddingRelative(
+                    horizontalTileLayout.getPaddingStart(),
+                    horizontalTileLayout.getPaddingTop(),
+                    horizontalTileLayout.getPaddingEnd(),
+                    mContext.getResources().getDimensionPixelSize(
+                            R.dimen.qqs_horizonal_tile_padding_bottom));
+            mHorizontalTileLayout = horizontalTileLayout;
             mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger);
-            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
-            lp.setMarginEnd(0);
-            lp.setMarginStart(marginSize);
-            mHorizontalLinearLayout.addView((View) mTileLayout, lp);
+            LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
+            int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
+            lp.setMarginStart(0);
+            lp.setMarginEnd(marginSize);
+            lp.gravity = Gravity.CENTER_VERTICAL;
+            mHorizontalLinearLayout.addView((View) mHorizontalTileLayout, lp);
 
             sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
 
+            boolean useHorizontal = shouldUseHorizontalTileLayout();
+            mTileLayout = useHorizontal ? mHorizontalTileLayout : mRegularTileLayout;
             mTileLayout.setListening(mListening);
             addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */);
-            ((View) mRegularTileLayout).setVisibility(View.GONE);
+            ((View) mRegularTileLayout).setVisibility(!useHorizontal ? View.VISIBLE : View.GONE);
+            mHorizontalLinearLayout.setVisibility(useHorizontal ? View.VISIBLE : View.GONE);
             addView((View) mRegularTileLayout, 0);
             super.setPadding(0, 0, 0, 0);
+            applySideMargins(mHorizontalLinearLayout);
+            applyBottomMargin((View) mRegularTileLayout);
         } else {
             sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
             mTileLayout = new HeaderTileLayout(context, mUiEventLogger);
             mTileLayout.setListening(mListening);
             addView((View) mTileLayout, 0 /* Between brightness and footer */);
             super.setPadding(0, 0, 0, 0);
+            applyBottomMargin((View) mTileLayout);
         }
     }
 
-    public QuickQSMediaPlayer getMediaPlayer() {
-        return mMediaPlayer;
+    private void applyBottomMargin(View view) {
+        int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_bottom);
+        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+        layoutParams.bottomMargin = margin;
+        view.setLayoutParams(layoutParams);
+    }
+
+    private void applySideMargins(View view) {
+        int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_horizontal);
+        MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+        layoutParams.setMarginStart(margin);
+        layoutParams.setMarginEnd(margin);
+        view.setLayoutParams(layoutParams);
+    }
+
+    private void reAttachMediaHost() {
+        if (mMediaHost == null) {
+            return;
+        }
+        boolean horizontal = shouldUseHorizontalTileLayout();
+        ViewGroup host = mMediaHost.getHostView();
+        ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
+        ViewGroup currentParent = (ViewGroup) host.getParent();
+        if (currentParent != newParent) {
+            if (currentParent != null) {
+                currentParent.removeView(host);
+            }
+            newParent.addView(host);
+            LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams();
+            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.weight = horizontal ? 1.5f : 0;
+            layoutParams.bottomMargin = mMediaBottomMargin;
+            int marginStart = horizontal
+                    ? getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_horizontal)
+                    : 0;
+            layoutParams.setMarginStart(marginStart);
+        }
+    }
+
+    @Override
+    protected void addMediaHostView() {
+        mMediaHost.setVisibleChangedListener((visible) -> {
+            switchTileLayout();
+            return null;
+        });
+        mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
+        mMediaHost.setExpansion(0.0f);
+        mMediaHost.setShowsOnlyActiveMedia(true);
+        reAttachMediaHost();
     }
 
     @Override
@@ -191,10 +240,19 @@
         super.drawTile(r, state);
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (newConfig.orientation != mLastOrientation) {
+            mLastOrientation = newConfig.orientation;
+            switchTileLayout();
+        }
+    }
+
     boolean switchTileLayout() {
         if (!mUsingMediaPlayer) return false;
-        mHasMediaPlayer = mMediaPlayer.hasMediaSession();
-        if (mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.GONE) {
+        mShowHorizontalTileLayout = shouldUseHorizontalTileLayout();
+        if (mShowHorizontalTileLayout && mHorizontalLinearLayout.getVisibility() == View.GONE) {
             mHorizontalLinearLayout.setVisibility(View.VISIBLE);
             ((View) mRegularTileLayout).setVisibility(View.GONE);
             mTileLayout.setListening(false);
@@ -202,11 +260,13 @@
                 mTileLayout.removeTile(record);
                 record.tile.removeCallback(record.callback);
             }
-            mTileLayout = mMediaTileLayout;
+            mTileLayout = mHorizontalTileLayout;
             if (mHost != null) setTiles(mHost.getTiles());
             mTileLayout.setListening(mListening);
+            reAttachMediaHost();
             return true;
-        } else if (!mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
+        } else if (!mShowHorizontalTileLayout
+                && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
             mHorizontalLinearLayout.setVisibility(View.GONE);
             ((View) mRegularTileLayout).setVisibility(View.VISIBLE);
             mTileLayout.setListening(false);
@@ -217,14 +277,21 @@
             mTileLayout = mRegularTileLayout;
             if (mHost != null) setTiles(mHost.getTiles());
             mTileLayout.setListening(mListening);
+            reAttachMediaHost();
             return true;
         }
         return false;
     }
 
-    /** Returns true if this panel currently contains a media player. */
-    public boolean hasMediaPlayer() {
-        return mHasMediaPlayer;
+    private boolean shouldUseHorizontalTileLayout() {
+        return mMediaHost.getVisible()
+                && getResources().getConfiguration().orientation
+                        == Configuration.ORIENTATION_LANDSCAPE;
+    }
+
+    /** Returns true if this panel currently uses a horizontal tile layout. */
+    public boolean usesHorizontalLayout() {
+        return mShowHorizontalTileLayout;
     }
 
     @Override
@@ -341,7 +408,7 @@
             setClipChildren(false);
             setClipToPadding(false);
             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.MATCH_PARENT);
+                    LayoutParams.WRAP_CONTENT);
             lp.gravity = Gravity.CENTER_HORIZONTAL;
             setLayoutParams(lp);
         }
@@ -354,6 +421,7 @@
 
         @Override
         public void onFinishInflate(){
+            super.onFinishInflate();
             updateResources();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index b15c6a3..3b2bea8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -220,6 +220,10 @@
         mNextAlarmTextView.setSelected(true);
     }
 
+    public QuickQSPanel getHeaderQsPanel() {
+        return mHeaderQsPanel;
+    }
+
     private List<String> getIgnoredIconSlots() {
         ArrayList<String> ignored = new ArrayList<>();
         ignored.add(mContext.getResources().getString(
@@ -336,23 +340,6 @@
                 com.android.internal.R.dimen.quick_qs_offset_height);
         mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
 
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-
-        if (mQsDisabled) {
-            lp.height = resources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.quick_qs_offset_height);
-        } else if (useQsMediaPlayer(mContext) && mHeaderQsPanel.hasMediaPlayer()) {
-            lp.height = Math.max(getMinimumHeight(),
-                    resources.getDimensionPixelSize(
-                            com.android.internal.R.dimen.quick_qs_total_height_with_media));
-        } else {
-            lp.height = Math.max(getMinimumHeight(),
-                    resources.getDimensionPixelSize(
-                            com.android.internal.R.dimen.quick_qs_total_height));
-        }
-
-        setLayoutParams(lp);
-
         updateStatusIconAlphaAnimator();
         updateHeaderTextContainerAlphaAnimator();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 6249f82..aa63b40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -106,6 +106,7 @@
     }
 
     public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
         mName.setEnabled(enabled);
         mAvatar.setEnabled(enabled);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index c2473280..abd7e71 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -72,6 +72,7 @@
         window.getDecorView();
         window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
         window.setGravity(Gravity.TOP);
+        setTitle(R.string.screenrecord_name);
 
         setContentView(R.layout.screen_record_dialog);
 
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index e67b3d7..02a7aca 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -813,4 +813,12 @@
             updateVisibility(true /* visible */);
         }
     }
+
+    /** @return the container token for the secondary split root task. */
+    public WindowContainerToken getSecondaryRoot() {
+        if (mSplits == null || mSplits.mSecondary == null) {
+            return null;
+        }
+        return mSplits.mSecondary.token;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index e32d174..9f4932e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -44,14 +44,17 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.KeyguardMediaPlayer;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -113,7 +116,6 @@
     private ScrimController mScrimController;
     @Nullable
     private LockscreenWallpaper mLockscreenWallpaper;
-    private final KeyguardMediaPlayer mMediaPlayer;
 
     private final Executor mMainExecutor;
 
@@ -187,13 +189,12 @@
             NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController,
-            KeyguardMediaPlayer keyguardMediaPlayer,
             @Main Executor mainExecutor,
-            DeviceConfigProxy deviceConfig) {
+            DeviceConfigProxy deviceConfig,
+            MediaDataManager mediaDataManager) {
         mContext = context;
         mMediaArtworkProcessor = mediaArtworkProcessor;
         mKeyguardBypassController = keyguardBypassController;
-        mMediaPlayer = keyguardMediaPlayer;
         mMediaListeners = new ArrayList<>();
         // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
         // in session state
@@ -204,14 +205,26 @@
         mNotificationShadeWindowController = notificationShadeWindowController;
         mEntryManager = notificationEntryManager;
         mMainExecutor = mainExecutor;
+
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+
             @Override
             public void onPendingEntryAdded(NotificationEntry entry) {
-                findAndUpdateMediaNotifications();
+                mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
             }
 
             @Override
             public void onPreEntryUpdated(NotificationEntry entry) {
+                mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+            }
+
+            @Override
+            public void onEntryInflated(NotificationEntry entry) {
+                findAndUpdateMediaNotifications();
+            }
+
+            @Override
+            public void onEntryReinflated(NotificationEntry entry) {
                 findAndUpdateMediaNotifications();
             }
 
@@ -222,6 +235,7 @@
                     boolean removedByUser,
                     int reason) {
                 onNotificationRemoved(entry.getKey());
+                mediaDataManager.onNotificationRemoved(entry.getKey());
             }
         });
 
@@ -278,7 +292,7 @@
 
     public void addCallback(MediaListener callback) {
         mMediaListeners.add(callback);
-        callback.onMetadataOrStateChanged(mMediaMetadata,
+        callback.onPrimaryMetadataOrStateChanged(mMediaMetadata,
                 getMediaControllerPlaybackState(mMediaController));
     }
 
@@ -392,7 +406,7 @@
         @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
         ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
         for (int i = 0; i < callbacks.size(); i++) {
-            callbacks.get(i).onMetadataOrStateChanged(mMediaMetadata, state);
+            callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state);
         }
     }
 
@@ -473,7 +487,6 @@
             && mBiometricUnlockController.isWakeAndUnlock();
         if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
             mBackdrop.setVisibility(View.INVISIBLE);
-            mMediaPlayer.clearControls();
             Trace.endSection();
             return;
         }
@@ -496,14 +509,6 @@
             }
         }
 
-        NotificationEntry entry = mEntryManager
-                .getActiveNotificationUnfiltered(mMediaNotificationKey);
-        if (entry != null) {
-            mMediaPlayer.updateControls(entry, getMediaIcon(), mediaMetadata);
-        } else {
-            mMediaPlayer.clearControls();
-        }
-
         // Process artwork on a background thread and send the resulting bitmap to
         // finishUpdateMediaMetaData.
         if (metaDataChanged) {
@@ -626,7 +631,6 @@
                     // We are unlocking directly - no animation!
                     mBackdrop.setVisibility(View.GONE);
                     mBackdropBack.setImageDrawable(null);
-                    mMediaPlayer.clearControls();
                     if (windowController != null) {
                         windowController.setBackdropShowing(false);
                     }
@@ -643,7 +647,6 @@
                                 mBackdrop.setVisibility(View.GONE);
                                 mBackdropFront.animate().cancel();
                                 mBackdropBack.setImageDrawable(null);
-                                mMediaPlayer.clearControls();
                                 mMainExecutor.execute(mHideBackdropFront);
                             });
                     if (mKeyguardStateController.isKeyguardFadingAway()) {
@@ -750,6 +753,7 @@
          * @param state Current playback state
          * @see PlaybackState.State
          */
-        void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state);
+        default void onPrimaryMetadataOrStateChanged(MediaMetadata metadata,
+                @PlaybackState.State int state) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 3cb2a2a..1079f10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -52,7 +52,7 @@
     /**
      * Updates the visual representation of the notifications.
      */
-    void updateNotificationViews();
+    void updateNotificationViews(String reason);
 
     /**
      * Returns the maximum number of notifications to show while locked.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 02c98ae..88f148b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -121,14 +121,14 @@
     }
 
     override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
-        return canHandleMotionEvent() && startExpansion(event)
+        return maybeStartExpansion(event)
     }
 
-    private fun canHandleMotionEvent(): Boolean {
-        return !wakeUpCoordinator.canShowPulsingHuns || qsExpanded || bouncerShowing
-    }
-
-    private fun startExpansion(event: MotionEvent): Boolean {
+    private fun maybeStartExpansion(event: MotionEvent): Boolean {
+        if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded ||
+                bouncerShowing) {
+            return false
+        }
         if (velocityTracker == null) {
             velocityTracker = VelocityTracker.obtain()
         }
@@ -177,14 +177,9 @@
     }
 
     override fun onTouchEvent(event: MotionEvent): Boolean {
-        if (!canHandleMotionEvent()) {
-            return false
+        if (!isExpanding) {
+            return maybeStartExpansion(event)
         }
-
-        if (!isExpanding || event.actionMasked == MotionEvent.ACTION_DOWN) {
-            return startExpansion(event)
-        }
-
         velocityTracker!!.addMovement(event)
         val y = event.y
 
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 de7e36d9..f0fed13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,9 +21,9 @@
 import android.os.Handler;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardMediaPlayer;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.MediaArtworkProcessor;
@@ -95,9 +95,9 @@
             NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController,
-            KeyguardMediaPlayer keyguardMediaPlayer,
             @Main Executor mainExecutor,
-            DeviceConfigProxy deviceConfigProxy) {
+            DeviceConfigProxy deviceConfigProxy,
+            MediaDataManager mediaDataManager) {
         return new NotificationMediaManager(
                 context,
                 statusBarLazy,
@@ -105,9 +105,9 @@
                 notificationEntryManager,
                 mediaArtworkProcessor,
                 keyguardBypassController,
-                keyguardMediaPlayer,
                 mainExecutor,
-                deviceConfigProxy);
+                deviceConfigProxy,
+                mediaDataManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index 75b41ca..eee9cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.notification;
 
+import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.Property;
 import android.view.View;
 
@@ -35,6 +37,100 @@
     public static final AnimatableProperty Y = AnimatableProperty.from(View.Y,
             R.id.y_animator_tag, R.id.y_animator_tag_start_value, R.id.y_animator_tag_end_value);
 
+    /**
+     * Similar to X, however this doesn't allow for any other modifications other than from this
+     * property. When using X, it's possible that the view is laid out during the animation,
+     * which could break the continuity
+     */
+    public static final AnimatableProperty ABSOLUTE_X = AnimatableProperty.from(
+            new FloatProperty<View>("ViewAbsoluteX") {
+                @Override
+                public void setValue(View view, float value) {
+                    view.setTag(R.id.absolute_x_current_value, value);
+                    View.X.set(view, value);
+                }
+
+                @Override
+                public Float get(View view) {
+                    Object tag = view.getTag(R.id.absolute_x_current_value);
+                    if (tag instanceof Float) {
+                        return (Float) tag;
+                    }
+                    return View.X.get(view);
+                }
+            },
+            R.id.absolute_x_animator_tag,
+            R.id.absolute_x_animator_start_tag,
+            R.id.absolute_x_animator_end_tag);
+
+    /**
+     * Similar to Y, however this doesn't allow for any other modifications other than from this
+     * property. When using X, it's possible that the view is laid out during the animation,
+     * which could break the continuity
+     */
+    public static final AnimatableProperty ABSOLUTE_Y = AnimatableProperty.from(
+            new FloatProperty<View>("ViewAbsoluteY") {
+                @Override
+                public void setValue(View view, float value) {
+                    view.setTag(R.id.absolute_y_current_value, value);
+                    View.Y.set(view, value);
+                }
+
+                @Override
+                public Float get(View view) {
+                    Object tag = view.getTag(R.id.absolute_y_current_value);
+                    if (tag instanceof Float) {
+                        return (Float) tag;
+                    }
+                    return View.Y.get(view);
+                }
+            },
+            R.id.absolute_y_animator_tag,
+            R.id.absolute_y_animator_start_tag,
+            R.id.absolute_y_animator_end_tag);
+
+    public static final AnimatableProperty WIDTH = AnimatableProperty.from(
+            new FloatProperty<View>("ViewWidth") {
+                @Override
+                public void setValue(View view, float value) {
+                    view.setTag(R.id.view_width_current_value, value);
+                    view.setRight((int) (view.getLeft() + value));
+                }
+
+                @Override
+                public Float get(View view) {
+                    Object tag = view.getTag(R.id.view_width_current_value);
+                    if (tag instanceof Float) {
+                        return (Float) tag;
+                    }
+                    return (float) view.getWidth();
+                }
+            },
+            R.id.view_width_animator_tag,
+            R.id.view_width_animator_start_tag,
+            R.id.view_width_animator_end_tag);
+
+    public static final AnimatableProperty HEIGHT = AnimatableProperty.from(
+            new FloatProperty<View>("ViewHeight") {
+                @Override
+                public void setValue(View view, float value) {
+                    view.setTag(R.id.view_height_current_value, value);
+                    view.setBottom((int) (view.getTop() + value));
+                }
+
+                @Override
+                public Float get(View view) {
+                    Object tag = view.getTag(R.id.view_height_current_value);
+                    if (tag instanceof Float) {
+                        return (Float) tag;
+                    }
+                    return (float) view.getHeight();
+                }
+            },
+            R.id.view_height_animator_tag,
+            R.id.view_height_animator_start_tag,
+            R.id.view_height_animator_end_tag);
+
     public abstract int getAnimationStartTag();
 
     public abstract int getAnimationEndTag();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index d251777..d647124 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -674,7 +674,7 @@
     public void updateNotifications(String reason) {
         reapplyFilterAndSort(reason);
         if (mPresenter != null && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mPresenter.updateNotificationViews();
+            mPresenter.updateNotificationViews(reason);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 1f9d3af..b1b6a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,13 +34,20 @@
  */
 public class PropertyAnimator {
 
+    /**
+     * Set a property on a view, updating its value, even if it's already animating.
+     * The @param animated can be used to request an animation.
+     * If the view isn't animated, this utility will update the current animation if existent,
+     * such that the end value will point to @param newEndValue or apply it directly if there's
+     * no animation.
+     */
     public static <T extends View> void setProperty(final T view,
             AnimatableProperty animatableProperty, float newEndValue,
             AnimationProperties properties, boolean animated) {
         int animatorTag = animatableProperty.getAnimatorTag();
         ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
         if (previousAnimator != null || animated) {
-            startAnimation(view, animatableProperty, newEndValue, properties);
+            startAnimation(view, animatableProperty, newEndValue, animated ? properties : null);
         } else {
             // no new animation needed, let's just apply the value
             animatableProperty.getProperty().set(view, newEndValue);
@@ -60,8 +67,8 @@
         }
         int animatorTag = animatableProperty.getAnimatorTag();
         ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
-        AnimationFilter filter = properties.getAnimationFilter();
-        if (!filter.shouldAnimateProperty(property)) {
+        AnimationFilter filter = properties != null ? properties.getAnimationFilter() : null;
+        if (filter == null || !filter.shouldAnimateProperty(property)) {
             // just a local update was performed
             if (previousAnimator != null) {
                 // we need to increase all animation keyframes of the previous animator by the
@@ -82,6 +89,14 @@
         }
 
         Float currentValue = property.get(view);
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property);
+        if (currentValue.equals(newEndValue)) {
+            // Skip the animation!
+            if (listener != null) {
+                listener.onAnimationEnd(null);
+            }
+            return;
+        }
         ValueAnimator animator = ValueAnimator.ofFloat(currentValue, newEndValue);
         animator.addUpdateListener(
                 animation -> property.set(view, (Float) animation.getAnimatedValue()));
@@ -96,7 +111,6 @@
                 || previousAnimator.getAnimatedFraction() == 0)) {
             animator.setStartDelay(properties.delay);
         }
-        AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property);
         if (listener != null) {
             animator.addListener(listener);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cb0c283..634872d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -209,7 +209,7 @@
     }
 
     /** The key for this notification. Guaranteed to be immutable and unique */
-    public String getKey() {
+    @NonNull public String getKey() {
         return mKey;
     }
 
@@ -217,7 +217,7 @@
      * The StatusBarNotification that represents one half of a NotificationEntry (the other half
      * being the Ranking). This object is swapped out whenever a notification is updated.
      */
-    public StatusBarNotification getSbn() {
+    @NonNull public StatusBarNotification getSbn() {
         return mSbn;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 59f119e..3fab6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.notifcollection;
 
+import android.annotation.NonNull;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 
@@ -43,13 +44,13 @@
      * there is no guarantee of order and they may not have had a chance to initialize yet. Instead,
      * use {@link #onEntryAdded} which is called after all initialization.
      */
-    default void onEntryInit(NotificationEntry entry) {
+    default void onEntryInit(@NonNull NotificationEntry entry) {
     }
 
     /**
      * Called whenever a notification with a new key is posted.
      */
-    default void onEntryAdded(NotificationEntry entry) {
+    default void onEntryAdded(@NonNull NotificationEntry entry) {
     }
 
     /**
@@ -64,7 +65,7 @@
      * immediately after a user dismisses a notification: we wait until we receive confirmation from
      * system server before considering the notification removed.
      */
-    default void onEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
+    default void onEntryRemoved(@NonNull NotificationEntry entry, @CancellationReason int reason) {
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index 88888d1..0fd865b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -75,7 +75,7 @@
         mediaManager.addCallback(this)
     }
 
-    override fun onMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
+    override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
         val previous = currentMediaEntry
         var newEntry = entryManager
                 .getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 5797944..0831c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -767,6 +767,10 @@
         return mContentTranslation;
     }
 
+    public boolean wantsAddAndRemoveAnimations() {
+        return true;
+    }
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index 0ccebc13..56f8e08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -109,7 +109,7 @@
     }
 
     @Nullable
-    private CharSequence resolveText(Notification notification) {
+    public static CharSequence resolveText(Notification notification) {
         CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
         if (contentText == null) {
             contentText = notification.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
@@ -118,7 +118,7 @@
     }
 
     @Nullable
-    private CharSequence resolveTitle(Notification notification) {
+    public static CharSequence resolveTitle(Notification notification) {
         CharSequence titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
         if (titleText == null) {
             titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index c67c016..a9491c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -191,38 +191,6 @@
         final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
                 .getParcelable(Notification.EXTRA_MEDIA_SESSION);
 
-        if (Utils.useQsMediaPlayer(mContext) && token != null) {
-            final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
-                    .getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
-            int tintColor = getNotificationHeader().getOriginalIconColor();
-            NotificationShadeWindowController ctrl = Dependency.get(
-                    NotificationShadeWindowController.class);
-            QuickQSPanel panel = ctrl.getNotificationShadeView().findViewById(
-                    com.android.systemui.R.id.quick_qs_panel);
-            StatusBarNotification sbn = mRow.getEntry().getSbn();
-            Notification notif = sbn.getNotification();
-            Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
-            panel.getMediaPlayer().setMediaSession(token,
-                    iconDrawable,
-                    notif.getLargeIcon(),
-                    tintColor,
-                    mBackgroundColor,
-                    mActions,
-                    compactActions,
-                    notif.contentIntent,
-                    sbn.getKey());
-            QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
-                    com.android.systemui.R.id.quick_settings_panel);
-            bigPanel.addMediaSession(token,
-                    iconDrawable,
-                    notif.getLargeIcon(),
-                    tintColor,
-                    mBackgroundColor,
-                    mActions,
-                    sbn,
-                    sbn.getKey());
-        }
-
         boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
         if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
             if (mSeekBarView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
index ab055e1..3ac322f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -37,7 +38,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mContentView = findViewById(R.id.keyguard_media_view);
     }
 
     @Override
@@ -52,4 +52,17 @@
     public void setBackgroundColor(int color) {
         setTintColor(color);
     }
+
+    public void setContentView(ViewGroup contentView) {
+        mContentView = contentView;
+        addView(contentView);
+        ViewGroup.LayoutParams layoutParams = contentView.getLayoutParams();
+        layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+    }
+
+    @Override
+    public boolean wantsAddAndRemoveAnimations() {
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
new file mode 100644
index 0000000..9cf1f74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationSectionLog
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val TAG = "NotifSections"
+
+@Singleton
+class NotificationSectionsLogger @Inject constructor(
+    @NotificationSectionLog private val logBuffer: LogBuffer
+) {
+
+    fun logStartSectionUpdate(reason: String) = logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = reason },
+            { "Updating section boundaries: $reason" }
+    )
+
+    fun logStr(str: String) = logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = str },
+            { str1 ?: "" }
+    )
+
+    fun logPosition(position: Int, label: String) = logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = position
+                str1 = label
+            },
+            { "$int1: $str1" }
+    )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 6eec1ca..dcf30ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -29,10 +29,12 @@
 import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.KeyguardMediaPlayer;
 import com.android.systemui.R;
+import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -74,17 +76,17 @@
     private final StatusBarStateController mStatusBarStateController;
     private final ConfigurationController mConfigurationController;
     private final PeopleHubViewAdapter mPeopleHubViewAdapter;
-    private final KeyguardMediaPlayer mKeyguardMediaPlayer;
     private final NotificationSectionsFeatureManager mSectionsFeatureManager;
+    private final KeyguardMediaController mKeyguardMediaController;
     private final int mNumberOfSections;
-
+    private final NotificationSectionsLogger mLogger;
     private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
         @Override
         public void setVisible(boolean isVisible) {
             if (mPeopleHubVisible != isVisible) {
                 mPeopleHubVisible = isVisible;
                 if (mInitialized) {
-                    updateSectionBoundaries();
+                    updateSectionBoundaries("PeopleHub visibility changed");
                 }
             }
         }
@@ -123,15 +125,18 @@
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
             PeopleHubViewAdapter peopleHubViewAdapter,
-            KeyguardMediaPlayer keyguardMediaPlayer,
-            NotificationSectionsFeatureManager sectionsFeatureManager) {
+            KeyguardMediaController keyguardMediaController,
+            NotificationSectionsFeatureManager sectionsFeatureManager,
+            NotificationSectionsLogger logger) {
+
         mActivityStarter = activityStarter;
         mStatusBarStateController = statusBarStateController;
         mConfigurationController = configurationController;
         mPeopleHubViewAdapter = peopleHubViewAdapter;
-        mKeyguardMediaPlayer = keyguardMediaPlayer;
         mSectionsFeatureManager = sectionsFeatureManager;
         mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
+        mKeyguardMediaController = keyguardMediaController;
+        mLogger = logger;
     }
 
     NotificationSection[] createSectionsForBuckets() {
@@ -205,12 +210,9 @@
         mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
         mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
 
-        if (mMediaControlsView != null) {
-            mKeyguardMediaPlayer.unbindView();
-        }
         mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
                 R.layout.keyguard_media_header);
-        mKeyguardMediaPlayer.bindView(mMediaControlsView);
+        mKeyguardMediaController.attach(mMediaControlsView);
     }
 
     /** Listener for when the "clear all" button is clicked on the gentle notification header. */
@@ -250,15 +252,70 @@
         return null;
     }
 
+    private void logShadeContents() {
+        final int childCount = mParent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mParent.getChildAt(i);
+            if (child == mIncomingHeader) {
+                mLogger.logPosition(i, "INCOMING HEADER");
+                continue;
+            }
+            if (child == mMediaControlsView) {
+                mLogger.logPosition(i, "MEDIA CONTROLS");
+                continue;
+            }
+            if (child == mPeopleHubView) {
+                mLogger.logPosition(i, "CONVERSATIONS HEADER");
+                continue;
+            }
+            if (child == mAlertingHeader) {
+                mLogger.logPosition(i, "ALERTING HEADER");
+                continue;
+            }
+            if (child == mGentleHeader) {
+                mLogger.logPosition(i, "SILENT HEADER");
+                continue;
+            }
+
+            if (!(child instanceof ExpandableNotificationRow)) {
+                mLogger.logPosition(i, "other:" + child.getClass().getName());
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            // Once we enter a new section, calculate the target position for the header.
+            switch (row.getEntry().getBucket()) {
+                case BUCKET_HEADS_UP:
+                    mLogger.logPosition(i, "Heads Up");
+                    break;
+                case BUCKET_PEOPLE:
+                    mLogger.logPosition(i, "Conversation");
+                    break;
+                case BUCKET_ALERTING:
+                    mLogger.logPosition(i, "Alerting");
+                    break;
+                case BUCKET_SILENT:
+                    mLogger.logPosition(i, "Silent");
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void updateSectionBoundaries() {
+        updateSectionBoundaries("test");
+    }
+
     /**
      * Should be called whenever notifs are added, removed, or updated. Updates section boundary
      * bookkeeping and adds/moves/removes section headers if appropriate.
      */
-    void updateSectionBoundaries() {
+    void updateSectionBoundaries(String reason) {
         if (!isUsingMultipleSections()) {
             return;
         }
 
+        mLogger.logStartSectionUpdate(reason);
+
         // The overall strategy here is to iterate over the current children of mParent, looking
         // for where the sections headers are currently positioned, and where each section begins.
         // Then, once we find the start of a new section, we track that position as the "target" for
@@ -267,7 +324,6 @@
 
         final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
         final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
-        final boolean isKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
         final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
 
         boolean peopleNotifsPresent = false;
@@ -275,7 +331,7 @@
         int currentMediaControlsIdx = -1;
         // Currently, just putting media controls in the front and incrementing the position based
         // on the number of heads-up notifs.
-        int mediaControlsTarget = isKeyguard && usingMediaControls ? 0 : -1;
+        int mediaControlsTarget = usingMediaControls ? 0 : -1;
         int currentIncomingHeaderIdx = -1;
         int incomingHeaderTarget = -1;
         int currentPeopleHeaderIdx = -1;
@@ -293,27 +349,33 @@
 
             // Track the existing positions of the headers
             if (child == mIncomingHeader) {
+                mLogger.logPosition(i, "INCOMING HEADER");
                 currentIncomingHeaderIdx = i;
                 continue;
             }
             if (child == mMediaControlsView) {
+                mLogger.logPosition(i, "MEDIA CONTROLS");
                 currentMediaControlsIdx = i;
                 continue;
             }
             if (child == mPeopleHubView) {
+                mLogger.logPosition(i, "CONVERSATIONS HEADER");
                 currentPeopleHeaderIdx = i;
                 continue;
             }
             if (child == mAlertingHeader) {
+                mLogger.logPosition(i, "ALERTING HEADER");
                 currentAlertingHeaderIdx = i;
                 continue;
             }
             if (child == mGentleHeader) {
+                mLogger.logPosition(i, "SILENT HEADER");
                 currentGentleHeaderIdx = i;
                 continue;
             }
 
             if (!(child instanceof ExpandableNotificationRow)) {
+                mLogger.logPosition(i, "other");
                 continue;
             }
             lastNotifIndex = i;
@@ -321,6 +383,7 @@
             // Once we enter a new section, calculate the target position for the header.
             switch (row.getEntry().getBucket()) {
                 case BUCKET_HEADS_UP:
+                    mLogger.logPosition(i, "Heads Up");
                     if (showHeaders && incomingHeaderTarget == -1) {
                         incomingHeaderTarget = i;
                         // Offset the target if there are other headers before this that will be
@@ -346,6 +409,7 @@
                     }
                     break;
                 case BUCKET_PEOPLE:
+                    mLogger.logPosition(i, "Conversation");
                     peopleNotifsPresent = true;
                     if (showHeaders && peopleHeaderTarget == -1) {
                         peopleHeaderTarget = i;
@@ -363,6 +427,7 @@
                     }
                     break;
                 case BUCKET_ALERTING:
+                    mLogger.logPosition(i, "Alerting");
                     if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
                         alertingHeaderTarget = i;
                         // Offset the target if there are other headers before this that will be
@@ -376,6 +441,7 @@
                     }
                     break;
                 case BUCKET_SILENT:
+                    mLogger.logPosition(i, "Silent");
                     if (showHeaders && gentleHeaderTarget == -1) {
                         gentleHeaderTarget = i;
                         // Offset the target if there are other headers before this that will be
@@ -406,6 +472,14 @@
             }
         }
 
+        mLogger.logStr("New header target positions:");
+
+        mLogger.logPosition(incomingHeaderTarget, "INCOMING HEADER");
+        mLogger.logPosition(mediaControlsTarget, "MEDIA CONTROLS");
+        mLogger.logPosition(peopleHeaderTarget, "CONVERSATIONS HEADER");
+        mLogger.logPosition(alertingHeaderTarget, "ALERTING HEADER");
+        mLogger.logPosition(gentleHeaderTarget, "SILENT HEADER");
+
         // Add headers in reverse order to preserve indices
         adjustHeaderVisibilityAndPosition(
                 gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
@@ -416,6 +490,13 @@
         adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
         adjustViewPosition(incomingHeaderTarget, mIncomingHeader, currentIncomingHeaderIdx);
 
+
+        mLogger.logStr("Final order:");
+
+        logShadeContents();
+
+        mLogger.logStr("Section boundary update complete");
+
         // Update headers to reflect state of section contents
         mGentleHeader.setAreThereDismissableGentleNotifs(
                 mParent.hasActiveClearableNotifications(ROWS_GENTLE));
@@ -588,7 +669,7 @@
 
     void hidePeopleRow() {
         mPeopleHubVisible = false;
-        updateSectionBoundaries();
+        updateSectionBoundaries("PeopleHub dismissed");
     }
 
     void setHeaderForegroundColor(@ColorInt int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7f32c00..1ccc2bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -29,7 +29,6 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.app.TaskStackBuilder;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeAnimator;
@@ -51,7 +50,6 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -3074,6 +3072,9 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private boolean generateRemoveAnimation(ExpandableView child) {
+        if (!child.wantsAddAndRemoveAnimations()) {
+            return false;
+        }
         if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
             mAddedHeadsUpChildren.remove(child);
             return false;
@@ -3428,7 +3429,8 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
-        if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) {
+        if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()
+                && child.wantsAddAndRemoveAnimations()) {
             // Generate Animations
             mChildrenToAddAnimated.add(child);
             if (fromMoreCard) {
@@ -5841,7 +5843,7 @@
         // Let's update the footer once the notifications have been updated (in the next frame)
         post(() -> {
             updateFooter();
-            updateSectionBoundaries();
+            updateSectionBoundaries("dynamic privacy changed");
         });
     }
 
@@ -5922,8 +5924,8 @@
 
     /** Updates the indices of the boundaries between sections. */
     @ShadeViewRefactor(RefactorComponent.INPUT)
-    public void updateSectionBoundaries() {
-        mSectionsManager.updateSectionBoundaries();
+    public void updateSectionBoundaries(String reason) {
+        mSectionsManager.updateSectionBoundaries(reason);
     }
 
     private void updateContinuousBackgroundDrawing() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 84dd48b..80785db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.annotation.Nullable;
-import android.app.NotificationChannel;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.Log;
@@ -28,6 +27,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -42,6 +42,8 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * A class to handle notifications and their corresponding groups.
  */
@@ -51,6 +53,7 @@
     private static final String TAG = "NotificationGroupManager";
     private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
     private final ArraySet<OnGroupChangeListener> mListeners = new ArraySet<>();
+    private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
     private int mBarState = -1;
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
     private HeadsUpManager mHeadsUpManager;
@@ -58,8 +61,11 @@
     @Nullable private BubbleController mBubbleController = null;
 
     @Inject
-    public NotificationGroupManager(StatusBarStateController statusBarStateController) {
+    public NotificationGroupManager(
+            StatusBarStateController statusBarStateController,
+            Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier) {
         statusBarStateController.addCallback(this);
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
     }
 
     private BubbleController getBubbleController() {
@@ -536,8 +542,9 @@
         if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
             return false;
         }
-        NotificationChannel channel = entry.getChannel();
-        if (channel != null && channel.isImportantConversation()) {
+        int peopleNotificationType = mPeopleNotificationIdentifier.get().getPeopleNotificationType(
+                entry.getSbn(), entry.getRanking());
+        if (peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON) {
             return true;
         }
         if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index c9716d3..35c33ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -69,6 +69,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -242,6 +243,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final ConversationNotificationManager mConversationNotificationManager;
+    private final MediaHierarchyManager mMediaHierarchyManager;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -456,7 +458,8 @@
             ConfigurationController configurationController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
-            ConversationNotificationManager conversationNotificationManager) {
+            ConversationNotificationManager conversationNotificationManager,
+            MediaHierarchyManager mediaHierarchyManager) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -466,6 +469,7 @@
         mZenModeController = zenModeController;
         mConfigurationController = configurationController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
+        mMediaHierarchyManager = mediaHierarchyManager;
         mView.setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
         mFalsingManager = falsingManager;
@@ -1609,7 +1613,7 @@
         if (mQs == null) return;
         float qsExpansionFraction = getQsExpansionFraction();
         mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
-        int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+        mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
         mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
     }
 
@@ -2880,8 +2884,8 @@
         return mNotificationStackScroller.createDelegate();
     }
 
-    public void updateNotificationViews() {
-        mNotificationStackScroller.updateSectionBoundaries();
+    void updateNotificationViews(String reason) {
+        mNotificationStackScroller.updateSectionBoundaries(reason);
         mNotificationStackScroller.updateSpeedBumpIndex();
         mNotificationStackScroller.updateFooter();
         updateShowEmptyShadeView();
@@ -3514,7 +3518,11 @@
             // Calculate quick setting heights.
             int oldMaxHeight = mQsMaxExpansionHeight;
             if (mQs != null) {
+                float previousMin = mQsMinExpansionHeight;
                 mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+                if (mQsExpansionHeight == previousMin) {
+                    mQsExpansionHeight = mQsMinExpansionHeight;
+                }
                 mQsMaxExpansionHeight = mQs.getDesiredHeight();
                 mNotificationStackScroller.setMaxTopPadding(
                         mQsMaxExpansionHeight + mQsNotificationTopPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 567ddb6..926c1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -279,7 +279,7 @@
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
             // Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
-            if (state.mKeyguardNeedsInput) {
+            if (state.mKeyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
                 mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
             } else {
                 mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -321,8 +321,7 @@
                 || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
                 || state.mHeadsUpShowing
                 || state.mScrimsVisibility != ScrimController.TRANSPARENT)
-                || state.mBackgroundBlurRadius > 0
-                || state.mLaunchingActivity;
+                || state.mBackgroundBlurRadius > 0;
     }
 
     private void applyFitsSystemWindows(State state) {
@@ -486,11 +485,6 @@
         apply(mCurrentState);
     }
 
-    void setLaunchingActivity(boolean launching) {
-        mCurrentState.mLaunchingActivity = launching;
-        apply(mCurrentState);
-    }
-
     public void setScrimsVisibility(int scrimsVisibility) {
         mCurrentState.mScrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
@@ -651,7 +645,6 @@
         boolean mForceCollapsed;
         boolean mForceDozeBrightness;
         boolean mForceUserActivity;
-        boolean mLaunchingActivity;
         boolean mBackdropShowing;
         boolean mWallpaperSupportsAmbientMode;
         boolean mNotTouchable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 0d25898..596a607 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -93,7 +93,6 @@
     private PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarTransitions mBarTransitions;
     private StatusBar mService;
-    private NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
     private boolean mDoubleTapEnabled;
     private boolean mSingleTapEnabled;
@@ -431,14 +430,10 @@
 
     public void setExpandAnimationPending(boolean pending) {
         mExpandAnimationPending = pending;
-        mNotificationShadeWindowController
-                .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
     }
 
     public void setExpandAnimationRunning(boolean running) {
         mExpandAnimationRunning = running;
-        mNotificationShadeWindowController
-                .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
     }
 
     public void cancelExpandHelper() {
@@ -461,9 +456,8 @@
         }
     }
 
-    public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
+    public void setService(StatusBar statusBar) {
         mService = statusBar;
-        mNotificationShadeWindowController = controller;
     }
 
     @VisibleForTesting
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 56510dd..d88cbab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1001,7 +1001,7 @@
         updateTheme();
 
         inflateStatusBarWindow();
-        mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
+        mNotificationShadeWindowViewController.setService(this);
         mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index aecbb90..84da35b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -296,20 +296,20 @@
     }
 
     @Override
-    public void updateNotificationViews() {
+    public void updateNotificationViews(final String reason) {
         // The function updateRowStates depends on both of these being non-null, so check them here.
         // We may be called before they are set from DeviceProvisionedController's callback.
         if (mScrimController == null) return;
 
         // Do not modify the notifications during collapse.
         if (isCollapsing()) {
-            mShadeController.addPostCollapseAction(this::updateNotificationViews);
+            mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
             return;
         }
 
         mViewHierarchyManager.updateNotificationViews();
 
-        mNotificationPanel.updateNotificationViews();
+        mNotificationPanel.updateNotificationViews(reason);
     }
 
     public void onNotificationRemoved(String key, StatusBarNotification old) {
@@ -347,7 +347,7 @@
             updateNotificationOnUiModeChanged();
             mDispatchUiModeChangeOnUserSwitched = false;
         }
-        updateNotificationViews();
+        updateNotificationViews("user switched");
         mMediaManager.clearCurrentMediaNotification();
         mStatusBar.setLockscreenUser(newUserId);
         updateMediaMetaData(true, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index f8da03a..df3748a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -298,6 +298,7 @@
             convertView.setAlpha(
                     item.isCurrent || item.isSwitchToEnabled ? USER_SWITCH_ENABLED_ALPHA
                             : USER_SWITCH_DISABLED_ALPHA);
+            convertView.setEnabled(item.isSwitchToEnabled);
             return convertView;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 6a9d9b6..b1792d0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -130,7 +130,7 @@
      */
     public static boolean useQsMediaPlayer(Context context) {
         int flag = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0);
+                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
         return flag > 0;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt b/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt
new file mode 100644
index 0000000..2be698b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.animation
+
+/**
+ * A class responsible for caching view Measurements which guarantees that we always obtain a value
+ */
+class GuaranteedMeasurementCache constructor(
+    private val baseCache : MeasurementCache,
+    private val inputMapper: (MeasurementInput) -> MeasurementInput,
+    private val measurementProvider: (MeasurementInput) -> MeasurementOutput?
+) : MeasurementCache {
+
+    override fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput {
+        val mappedInput = inputMapper.invoke(input)
+        if (!baseCache.contains(mappedInput)) {
+            var measurement = measurementProvider.invoke(mappedInput)
+            if (measurement != null) {
+                // Only cache measurings that actually have a size
+                baseCache.putMeasurement(mappedInput, measurement)
+            } else {
+                measurement = MeasurementOutput(0, 0)
+            }
+            return measurement
+        } else {
+            return baseCache.obtainMeasurement(mappedInput)
+        }
+    }
+
+    override fun contains(input: MeasurementInput): Boolean {
+        return baseCache.contains(inputMapper.invoke(input))
+    }
+
+    override fun putMeasurement(input: MeasurementInput, output: MeasurementOutput) {
+        if (output.measuredWidth == 0 || output.measuredHeight == 0) {
+            // Only cache measurings that actually have a size
+            return;
+        }
+        val remappedInput = inputMapper.invoke(input)
+        baseCache.putMeasurement(remappedInput, output)
+    }
+}
+
+/**
+ * A base implementation class responsible for caching view Measurements
+ */
+class BaseMeasurementCache : MeasurementCache {
+    private val dataCache: MutableMap<MeasurementInput, MeasurementOutput> = mutableMapOf()
+
+    override fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput {
+        val measurementOutput = dataCache[input]
+        if (measurementOutput == null) {
+            return MeasurementOutput(0, 0)
+        } else {
+            return measurementOutput
+        }
+    }
+
+    override fun contains(input: MeasurementInput) : Boolean {
+        return dataCache[input] != null
+    }
+
+    override fun putMeasurement(input: MeasurementInput, output: MeasurementOutput) {
+        dataCache[input] = output
+    }
+}
+
+interface MeasurementCache {
+    fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput
+    fun contains(input: MeasurementInput) : Boolean
+    fun putMeasurement(input: MeasurementInput, output: MeasurementOutput)
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
new file mode 100644
index 0000000..bf94c5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.animation
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+
+/**
+ * A special view that is designed to host a single "unique object". The unique object is
+ * dynamically added and removed from this view and may transition to other UniqueObjectHostViews
+ * available in the system.
+ * This is useful to share a singular instance of a view that can transition between completely
+ * independent parts of the view hierarchy.
+ * If the view currently hosts the unique object, it's measuring it normally,
+ * but if it's not attached, it will obtain the size by requesting a measure, as if it were
+ * always attached.
+ */
+class UniqueObjectHostView(
+    context: Context
+) : FrameLayout(context) {
+    lateinit var measurementCache : GuaranteedMeasurementCache
+    var onMeasureListener: ((MeasurementInput) -> Unit)? = null
+
+    @SuppressLint("DrawAllocation")
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        val paddingHorizontal = paddingStart + paddingEnd
+        val paddingVertical = paddingTop + paddingBottom
+        val width = MeasureSpec.getSize(widthMeasureSpec) - paddingHorizontal
+        val widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.getMode(widthMeasureSpec))
+        val height = MeasureSpec.getSize(heightMeasureSpec) - paddingVertical
+        val heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))
+        val measurementInput = MeasurementInputData(widthSpec, heightSpec)
+        onMeasureListener?.apply {
+            invoke(measurementInput)
+        }
+        if (!isCurrentHost()) {
+            // We're not currently the host, let's get the dimension from our cache (this might
+            // perform a measuring if the cache doesn't have it yet)
+            // The goal here is that the view will always have a consistent measuring, regardless
+            // if it's attached or not.
+            // The behavior is therefore very similar to the view being persistently attached to
+            // this host, which can prevent flickers. It also makes sure that we always know
+            // the size of the view during transitions even if it has never been attached here
+            // before.
+            val (cachedWidth, cachedHeight) = measurementCache.obtainMeasurement(measurementInput)
+            setMeasuredDimension(cachedWidth + paddingHorizontal, cachedHeight + paddingVertical)
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+            // Let's update our cache
+            val child = getChildAt(0)!!
+            val output = MeasurementOutput(child.measuredWidth, child.measuredHeight)
+            measurementCache.putMeasurement(measurementInput, output)
+        }
+    }
+
+    private fun isCurrentHost() = childCount != 0
+}
+
+/**
+ * A basic view measurement input
+ */
+interface MeasurementInput {
+    fun sameAs(input: MeasurementInput?): Boolean {
+        return equals(input)
+    }
+    val width : Int
+        get() {
+            return View.MeasureSpec.getSize(widthMeasureSpec)
+        }
+    val height : Int
+        get() {
+            return View.MeasureSpec.getSize(heightMeasureSpec)
+        }
+    var widthMeasureSpec: Int
+    var heightMeasureSpec: Int
+}
+
+/**
+ * The output of a view measurement
+ */
+data class MeasurementOutput(
+    val measuredWidth: Int,
+    val measuredHeight: Int
+)
+
+/**
+ * The data object holding a basic view measurement input
+ */
+data class MeasurementInputData(
+    override var widthMeasureSpec: Int,
+    override var heightMeasureSpec: Int
+) : MeasurementInput
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
deleted file mode 100644
index 4bcf917..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.app.Notification
-import android.graphics.drawable.Icon
-import android.media.MediaMetadata
-import android.media.session.MediaController
-import android.media.session.MediaSession
-import android.media.session.PlaybackState
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.widget.TextView
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.TaskExecutor
-import androidx.test.filters.SmallTest
-
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.media.MediaControllerFactory
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-public class KeyguardMediaPlayerTest : SysuiTestCase() {
-
-    private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
-    @Mock private lateinit var mockMediaFactory: MediaControllerFactory
-    @Mock private lateinit var mockMediaController: MediaController
-    private lateinit var playbackState: PlaybackState
-    private lateinit var fakeExecutor: FakeExecutor
-    private lateinit var mediaMetadata: MediaMetadata.Builder
-    private lateinit var entry: NotificationEntry
-    @Mock private lateinit var mockView: View
-    private lateinit var songView: TextView
-    private lateinit var artistView: TextView
-    @Mock private lateinit var mockIcon: Icon
-
-    private val taskExecutor: TaskExecutor = object : TaskExecutor() {
-        public override fun executeOnDiskIO(runnable: Runnable) {
-            runnable.run()
-        }
-        public override fun postToMainThread(runnable: Runnable) {
-            runnable.run()
-        }
-        public override fun isMainThread(): Boolean {
-            return true
-        }
-    }
-
-    @Before
-    public fun setup() {
-        playbackState = PlaybackState.Builder().run {
-            build()
-        }
-        mockMediaController = mock(MediaController::class.java)
-        whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState)
-        mockMediaFactory = mock(MediaControllerFactory::class.java)
-        whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController)
-
-        fakeExecutor = FakeExecutor(FakeSystemClock())
-        keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor)
-        mockIcon = mock(Icon::class.java)
-
-        mockView = mock(View::class.java)
-        songView = TextView(context)
-        artistView = TextView(context)
-        whenever<TextView>(mockView.findViewById(R.id.header_title)).thenReturn(songView)
-        whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
-
-        mediaMetadata = MediaMetadata.Builder()
-        entry = NotificationEntryBuilder().build()
-        entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION,
-                MediaSession.Token(1, null))
-
-        ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
-
-        keyguardMediaPlayer.bindView(mockView)
-    }
-
-    @After
-    public fun tearDown() {
-        keyguardMediaPlayer.unbindView()
-        ArchTaskExecutor.getInstance().setDelegate(null)
-    }
-
-    @Test
-    public fun testBind() {
-        keyguardMediaPlayer.unbindView()
-        keyguardMediaPlayer.bindView(mockView)
-    }
-
-    @Test
-    public fun testUnboundClearControls() {
-        keyguardMediaPlayer.unbindView()
-        keyguardMediaPlayer.clearControls()
-        keyguardMediaPlayer.bindView(mockView)
-    }
-
-    @Test
-    public fun testUpdateControls() {
-        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-        FakeExecutor.exhaustExecutors(fakeExecutor)
-        verify(mockView).setVisibility(View.VISIBLE)
-    }
-
-    @Test
-    public fun testClearControls() {
-        keyguardMediaPlayer.clearControls()
-        FakeExecutor.exhaustExecutors(fakeExecutor)
-        verify(mockView).setVisibility(View.GONE)
-    }
-
-    @Test
-    public fun testUpdateControlsNullPlaybackState() {
-        // GIVEN that the playback state is null (ie. the media session was destroyed)
-        whenever(mockMediaController.getPlaybackState()).thenReturn(null)
-        // WHEN updated
-        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-        FakeExecutor.exhaustExecutors(fakeExecutor)
-        // THEN the controls are cleared (ie. visibility is set to GONE)
-        verify(mockView).setVisibility(View.GONE)
-    }
-
-    @Test
-    public fun testSongName() {
-        val song: String = "Song"
-        mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song)
-
-        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-
-        assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
-        assertThat(songView.getText()).isEqualTo(song)
-    }
-
-    @Test
-    public fun testArtistName() {
-        val artist: String = "Artist"
-        mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist)
-
-        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-
-        assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
-        assertThat(artistView.getText()).isEqualTo(artist)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index b5609dd..7bc453a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,6 +38,7 @@
 
 import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
+import android.app.trust.IStrongAuthTracker;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -72,6 +73,8 @@
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -120,6 +123,10 @@
     @Mock
     private TrustManager mTrustManager;
     @Mock
+    private LockPatternUtils mLockPatternUtils;
+    @Mock
+    private ILockSettings mLockSettings;
+    @Mock
     private FingerprintManager mFingerprintManager;
     @Mock
     private FaceManager mFaceManager;
@@ -169,12 +176,13 @@
         when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
         when(mUserManager.isPrimaryUser()).thenReturn(true);
+        when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
         when(mStrongAuthTracker
                 .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
                 .thenReturn(true);
-
         when(mTelephonyManager.getServiceStateForSubscriber(anyInt()))
                 .thenReturn(new ServiceState());
+        when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
         mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
         mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
         mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
@@ -729,8 +737,9 @@
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
                     mBroadcastDispatcher, mDumpManager,
-                    mRingerModeTracker, mBackgroundExecutor, mStatusBarStateController);
-            mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
+                    mRingerModeTracker, mBackgroundExecutor,
+                    mStatusBarStateController, mLockPatternUtils);
+            setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
         public boolean hasSimStateJustChanged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index e2f303e..96e868d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -279,7 +279,8 @@
                 mFloatingContentCoordinator,
                 mDataRepository,
                 mSysUiState,
-                mock(INotificationManager.class));
+                mock(INotificationManager.class),
+                mWindowManager);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
         // Get a reference to the BubbleController's entry listener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 8a83b84..73b8760 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -254,7 +254,8 @@
                 mFloatingContentCoordinator,
                 mDataRepository,
                 mSysUiState,
-                mock(INotificationManager.class));
+                mock(INotificationManager.class),
+                mWindowManager);
         mBubbleController.addNotifCallback(mNotifCallback);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index 1542b86..bdb7944 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -18,6 +18,7 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
+import android.view.WindowManager;
 
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
@@ -57,13 +58,15 @@
             FloatingContentCoordinator floatingContentCoordinator,
             BubbleDataRepository dataRepository,
             SysUiState sysUiState,
-            INotificationManager notificationManager) {
+            INotificationManager notificationManager,
+            WindowManager windowManager) {
         super(context,
                 notificationShadeWindowController, statusBarStateController, shadeController,
                 data, Runnable::run, configurationController, interruptionStateProvider,
                 zenModeController, lockscreenUserManager, groupManager, entryManager,
                 notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
-                dataRepository, sysUiState, notificationManager);
+                dataRepository, sysUiState, notificationManager,
+                windowManager);
         setInflateSynchronously(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index 9629079..eb43b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -34,6 +34,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
+import com.android.systemui.SysuiTestCase;
+
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -41,7 +43,7 @@
 import java.util.function.BooleanSupplier;
 
 @LargeTest
-public class GlobalActionsImeTest {
+public class GlobalActionsImeTest extends SysuiTestCase {
 
     @Rule
     public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 92c1d76..f70fb4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -131,7 +131,7 @@
         MediaMetadata metadata = mock(MediaMetadata.class);
         when(metadata.getText(any())).thenReturn("metadata");
         mProvider.onDozingChanged(true);
-        mProvider.onMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
+        mProvider.onPrimaryMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
         mProvider.onBindSlice(mProvider.getUri());
         verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE));
         verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST));
@@ -144,7 +144,7 @@
         when(metadata.getText(any())).thenReturn("metadata");
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
-        mProvider.onMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
+        mProvider.onPrimaryMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
         mProvider.onBindSlice(mProvider.getUri());
         verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE));
         verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST));
@@ -210,7 +210,8 @@
         mProvider.onStateChanged(StatusBarState.KEYGUARD);
         mProvider.onDozingChanged(true);
         reset(mContentResolver);
-        mProvider.onMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING);
+        mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
+                PlaybackState.STATE_PLAYING);
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
 
         // Hides after waking up
@@ -222,7 +223,8 @@
     @Test
     public void onDozingChanged_updatesSliceIfMedia() {
         mProvider.onStateChanged(StatusBarState.KEYGUARD);
-        mProvider.onMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING);
+        mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
+                PlaybackState.STATE_PLAYING);
         reset(mContentResolver);
         // Show media when dozing
         mProvider.onDozingChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 128d6e5..6c543c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.customize.QSCustomizer;
@@ -50,7 +51,6 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,7 +62,6 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Collections;
-import java.util.concurrent.Executor;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -90,9 +89,7 @@
     @Mock
     private QSTileView mQSTileView;
     @Mock
-    private Executor mForegroundExecutor;
-    @Mock
-    private DelayableExecutor mBackgroundExecutor;
+    private MediaHost mMediaHost;
     @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
@@ -116,8 +113,7 @@
         mTestableLooper.runWithLooper(() -> {
             mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
             mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
-                    mQSLogger, mForegroundExecutor, mBackgroundExecutor,
-                    mLocalBluetoothManager, mActivityStarter, mEntryManager, mUiEventLogger);
+                    mQSLogger, mMediaHost, mUiEventLogger);
             // Provides a parent with non-zero size for QSPanel
             mParentView = new FrameLayout(mContext);
             mParentView.addView(mQsPanel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index bb7f73a..d583048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -243,7 +243,7 @@
         // Ensure that update callbacks happen in correct order
         InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
         order.verify(mEntryListener).onPreEntryUpdated(mEntry);
-        order.verify(mPresenter).updateNotificationViews();
+        order.verify(mPresenter).updateNotificationViews(any());
         order.verify(mEntryListener).onPostEntryUpdated(mEntry);
     }
 
@@ -254,7 +254,7 @@
 
         mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
 
-        verify(mPresenter).updateNotificationViews();
+        verify(mPresenter).updateNotificationViews(any());
         verify(mEntryListener).onEntryRemoved(
                 eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
         verify(mRow).setRemoved();
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 277ac24..595ba89 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
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -93,7 +94,9 @@
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
         mDependency.injectTestDependency(NotificationGroupManager.class,
-                new NotificationGroupManager(mock(StatusBarStateController.class)));
+                new NotificationGroupManager(
+                        mock(StatusBarStateController.class),
+                        () -> mock(PeopleNotificationIdentifier.class)));
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 2894abb8..7dfead7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -335,7 +335,7 @@
         assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
 
         // THEN we update the presenter
-        verify(mPresenter).updateNotificationViews();
+        verify(mPresenter).updateNotificationViews(any());
     }
 
     @Test
@@ -364,7 +364,7 @@
         verify(mEntryListener).onEntryReinflated(entry);
 
         // THEN we update the presenter
-        verify(mPresenter).updateNotificationViews();
+        verify(mPresenter).updateNotificationViews(any());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 07f2085..b9eb4d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -115,7 +115,9 @@
         dependency.injectMockDependency(BubbleController.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
-        mGroupManager = new NotificationGroupManager(mStatusBarStateController);
+        mGroupManager = new NotificationGroupManager(
+                mStatusBarStateController,
+                () -> mock(PeopleNotificationIdentifier.class));
         mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
                 mock(KeyguardBypassController.class), mock(NotificationGroupManager.class),
                 mock(ConfigurationControllerImpl.class));
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 646bc96..0b86a78 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
@@ -42,9 +42,9 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.KeyguardMediaPlayer;
 import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
@@ -74,10 +74,11 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private PeopleHubViewAdapter mPeopleHubAdapter;
-    @Mock private KeyguardMediaPlayer mKeyguardMediaPlayer;
+    @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
     @Mock private NotificationRowComponent mNotificationRowComponent;
     @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
+    @Mock private NotificationSectionsLogger mLogger;
 
     private NotificationSectionsManager mSectionsManager;
 
@@ -93,8 +94,9 @@
                         mStatusBarStateController,
                         mConfigurationController,
                         mPeopleHubAdapter,
-                        mKeyguardMediaPlayer,
-                        mSectionsFeatureManager
+                        mKeyguardMediaController,
+                        mSectionsFeatureManager,
+                        mLogger
                 );
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
@@ -367,38 +369,6 @@
         verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1);
     }
 
-    @Test
-    public void testMediaControls_RemoveWhenExitKeyguard() {
-        enableMediaControls();
-
-        // GIVEN a stack with media controls
-        setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER,
-                ChildType.GENTLE);
-
-        // WHEN we leave the keyguard
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // Then the media controls is removed
-        verify(mNssl).removeView(mSectionsManager.getMediaControlsView());
-    }
-
-    @Test
-    public void testMediaControls_RemoveWhenPullDownShade() {
-        enableMediaControls();
-
-        // GIVEN a stack with media controls
-        setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER,
-                ChildType.GENTLE);
-
-        // WHEN we pull down the shade on the keyguard
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
-        mSectionsManager.updateSectionBoundaries();
-
-        // Then the media controls is removed
-        verify(mNssl).removeView(mSectionsManager.getMediaControlsView());
-    }
-
     private void enablePeopleFiltering() {
         when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
         when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 67f94130..885dff3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
@@ -87,7 +88,9 @@
         when(mNotificationEntryManager.getPendingNotificationsIterator())
                 .thenReturn(mPendingEntries.values());
 
-        mGroupManager = new NotificationGroupManager(mock(StatusBarStateController.class));
+        mGroupManager = new NotificationGroupManager(
+                mock(StatusBarStateController.class),
+                () -> mock(PeopleNotificationIdentifier.class));
         mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
index 19ce1ea..5a6f74a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
@@ -33,6 +33,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
@@ -63,7 +64,9 @@
     }
 
     private void initializeGroupManager() {
-        mGroupManager = new NotificationGroupManager(mock(StatusBarStateController.class));
+        mGroupManager = new NotificationGroupManager(
+                mock(StatusBarStateController.class),
+                () -> mock(PeopleNotificationIdentifier.class));
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 57ef055..b5663d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -55,6 +55,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.doze.DozeLog;
+import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -172,6 +173,8 @@
     @Mock
     private ConfigurationController mConfigurationController;
     @Mock
+    private MediaHierarchyManager mMediaHiearchyManager;
+    @Mock
     private ConversationNotificationManager mConversationNotificationManager;
     private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
 
@@ -228,7 +231,7 @@
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
                 mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
                 mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
-                mConversationNotificationManager);
+                mConversationNotificationManager, mMediaHiearchyManager);
         mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
                 mNotificationShelf, mNotificationAreaController, mScrimController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
index 5320ecd..ca6c16f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
@@ -16,11 +16,15 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
 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.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -44,6 +48,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -62,6 +67,7 @@
     @Mock private SysuiColorExtractor mColorExtractor;
     @Mock ColorExtractor.GradientColors mGradientColors;
     @Mock private DumpManager mDumpManager;
+    @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
 
     private NotificationShadeWindowController mNotificationShadeWindowController;
 
@@ -121,4 +127,46 @@
         mNotificationShadeWindowController.setBackgroundBlurRadius(0);
         verify(mNotificationShadeWindowView).setVisibility(eq(View.INVISIBLE));
     }
+
+    @Test
+    public void setBouncerShowing_isFocusable_whenNeedsInput() {
+        mNotificationShadeWindowController.setKeyguardNeedsInput(true);
+        clearInvocations(mWindowManager);
+        mNotificationShadeWindowController.setBouncerShowing(true);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
+        assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
+    }
+
+    @Test
+    public void setKeyguardShowing_focusable_notAltFocusable_whenNeedsInput() {
+        mNotificationShadeWindowController.setKeyguardShowing(true);
+        clearInvocations(mWindowManager);
+        mNotificationShadeWindowController.setKeyguardNeedsInput(true);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
+        assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
+    }
+
+    @Test
+    public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
+        mNotificationShadeWindowController.setPanelExpanded(true);
+        clearInvocations(mWindowManager);
+        mNotificationShadeWindowController.setNotificationShadeFocusable(true);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
+        assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) != 0).isTrue();
+    }
+
+    @Test
+    public void setKeyguardShowing_notFocusable_byDefault() {
+        mNotificationShadeWindowController.setKeyguardShowing(false);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) != 0).isTrue();
+        assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index e04d25b..cc2d1c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -83,7 +83,6 @@
     @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
     @Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
-    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
 
     @Before
     public void setUp() {
@@ -122,7 +121,7 @@
                 mNotificationPanelViewController,
                 mStatusBarViewFactory);
         mController.setupExpandedStatusBar();
-        mController.setService(mStatusBar, mNotificationShadeWindowController);
+        mController.setService(mStatusBar);
         mController.setDragDownHelper(mDragDownHelper);
 
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 086a8be..ef17d13 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
 import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED;
 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
@@ -653,6 +654,10 @@
         return mService.isInlineSuggestionsEnabled();
     }
 
+    private boolean isViewFocusedLocked(int flags) {
+        return (flags & FLAG_VIEW_NOT_FOCUSED) == 0;
+    }
+
     /**
      * Clears the existing response for the partition, reads a new structure, and then requests a
      * new fill response from the fill service.
@@ -711,10 +716,13 @@
         cancelCurrentRequestLocked();
 
         // Only ask IME to create inline suggestions request if Autofill provider supports it and
-        // the render service is available.
+        // the render service is available except the autofill is triggered manually and the view
+        // is also not focused.
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
-        if (isInlineSuggestionsEnabledByAutofillProviderLocked() && remoteRenderService != null) {
+        if (isInlineSuggestionsEnabledByAutofillProviderLocked()
+                && remoteRenderService != null
+                && isViewFocusedLocked(flags)) {
             Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
                     mAssistReceiver.newAutofillRequestLocked(viewState,
                             /*isInlineRequest=*/ true);
@@ -3139,9 +3147,9 @@
                     }
                 };
 
-        // When the inline suggestion render service is available, there are 2 cases when
-        // augmented autofill should ask IME for inline suggestion request, because standard
-        // autofill flow didn't:
+        // When the inline suggestion render service is available and the view is focused, there
+        // are 2 cases when augmented autofill should ask IME for inline suggestion request,
+        // because standard autofill flow didn't:
         // 1. the field is augmented autofill only (when standard autofill provider is None or
         // when it returns null response)
         // 2. standard autofill provider doesn't support inline suggestion
@@ -3149,7 +3157,8 @@
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService != null
                 && (mForAugmentedAutofillOnly
-                || !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
+                || !isInlineSuggestionsEnabledByAutofillProviderLocked())
+                && isViewFocusedLocked(flags)) {
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
             remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
                     (extras) -> {
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index f688759..fa84427 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -315,4 +315,15 @@
      * @return {@code true} if the pruning was successful, {@code false} otherwise
      */
     public abstract boolean pruneUninstalledPackagesData(@UserIdInt int userId);
+
+    /**
+     * Called by {@link com.android.server.usage.UsageStatsIdleService} between 24 to 48 hours of
+     * when the user is first unlocked to update the usage stats package mappings data that might
+     * be stale or have existed from a restore and belongs to packages that are not installed for
+     * this user anymore.
+     * Note: this is only executed for the system user.
+     *
+     * @return {@code true} if the updating was successful, {@code false} otherwise
+     */
+    public abstract boolean updatePackageMappingsData();
 }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 905c489..6402e07 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1776,7 +1776,7 @@
             socketRecord =
                     userRecord.mEncapSocketRecords.getResourceOrThrow(c.getEncapSocketResourceId());
         }
-        SpiRecord spiRecord = userRecord.mSpiRecords.getResourceOrThrow(c.getSpiResourceId());
+        SpiRecord spiRecord = transformInfo.getSpiRecord();
 
         int mark =
                 (direction == IpSecManager.DIRECTION_OUT)
@@ -1809,7 +1809,7 @@
 
                 // Set outbound SPI only. We want inbound to use any valid SA (old, new) on rekeys,
                 // but want to guarantee outbound packets are sent over the new SA.
-                spi = transformInfo.getSpiRecord().getSpi();
+                spi = spiRecord.getSpi();
             }
 
             // Always update the policy with the relevant XFRM_IF_ID
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 29eb64a..62ea63a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1942,10 +1942,13 @@
             mDownloadsAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
         }
 
-        try {
-            mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null, mAppOpsCallback);
-            mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
-        } catch (RemoteException e) {
+        if (!mIsFuseEnabled) {
+            try {
+                mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null,
+                        mAppOpsCallback);
+                mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
+            } catch (RemoteException e) {
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 63e01e0..5ebfb00 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2582,6 +2582,28 @@
         }
     }
 
+    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+            int op, int uid, String packageName) {
+        boolean duplicate = false;
+        if (reports == null) {
+            reports = new ArrayList<>();
+        } else {
+            final int reportCount = reports.size();
+            for (int j = 0; j < reportCount; j++) {
+                ChangeRec report = reports.get(j);
+                if (report.op == op && report.pkg.equals(packageName)) {
+                    duplicate = true;
+                    break;
+                }
+            }
+        }
+        if (!duplicate) {
+            reports.add(new ChangeRec(op, uid, packageName));
+        }
+
+        return reports;
+    }
+
     private static HashMap<ModeCallback, ArrayList<ChangeRec>> addCallbacks(
             HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks,
             int op, int uid, String packageName, ArraySet<ModeCallback> cbs) {
@@ -2595,22 +2617,9 @@
         for (int i=0; i<N; i++) {
             ModeCallback cb = cbs.valueAt(i);
             ArrayList<ChangeRec> reports = callbacks.get(cb);
-            boolean duplicate = false;
-            if (reports == null) {
-                reports = new ArrayList<>();
-                callbacks.put(cb, reports);
-            } else {
-                final int reportCount = reports.size();
-                for (int j = 0; j < reportCount; j++) {
-                    ChangeRec report = reports.get(j);
-                    if (report.op == op && report.pkg.equals(packageName)) {
-                        duplicate = true;
-                        break;
-                    }
-                }
-            }
-            if (!duplicate) {
-                reports.add(new ChangeRec(op, uid, packageName));
+            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName);
+            if (changed != reports) {
+                callbacks.put(cb, changed);
             }
         }
         return callbacks;
@@ -2648,6 +2657,7 @@
         enforceManageAppOpsModes(callingPid, callingUid, reqUid);
 
         HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks = null;
+        ArrayList<ChangeRec> allChanges = new ArrayList<>();
         synchronized (this) {
             boolean changed = false;
             for (int i = mUidStates.size() - 1; i >= 0; i--) {
@@ -2668,6 +2678,9 @@
                                         mOpModeWatchers.get(code));
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mPackageModeWatchers.get(packageName));
+
+                                allChanges = addChange(allChanges, code, uidState.uid,
+                                        packageName);
                             }
                         }
                     }
@@ -2707,6 +2720,7 @@
                             callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
                                     mPackageModeWatchers.get(packageName));
 
+                            allChanges = addChange(allChanges, curOp.op, uid, packageName);
                             curOp.removeAttributionsWithNoTime();
                             if (curOp.mAttributions.isEmpty()) {
                                 pkgOps.removeAt(j);
@@ -2741,6 +2755,15 @@
                 }
             }
         }
+
+        if (allChanges != null) {
+            int numChanges = allChanges.size();
+            for (int i = 0; i < numChanges; i++) {
+                ChangeRec change = allChanges.get(i);
+                notifyOpChangedSync(change.op, change.uid, change.pkg,
+                        AppOpsManager.opToDefaultMode(change.op));
+            }
+        }
     }
 
     private void evalAllForegroundOpsLocked() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index d3feeda..5328350 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -211,7 +211,13 @@
                 }
                 mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
             } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
-                mForcedUseForComm = AudioSystem.FORCE_NONE;
+                if (mBtHelper.isBluetoothScoOn()) {
+                    mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+                    setForceUse_Async(
+                            AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
+                } else {
+                    mForcedUseForComm = AudioSystem.FORCE_NONE;
+                }
             }
 
             mForcedUseForCommExt = mForcedUseForComm;
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 036f4f5..9137996 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -412,8 +412,15 @@
                            BluetoothHeadset.STATE_AUDIO_DISCONNECTED)) {
                         mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
                         scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
-                        // startBluetoothSco called after stopBluetoothSco
-                        if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+                        // There are two cases where we want to immediately reconnect audio:
+                        // 1) If a new start request was received while disconnecting: this was
+                        // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
+                        // 2) If audio was connected then disconnected via Bluetooth APIs and
+                        // we still have pending activation requests by apps: this is indicated by
+                        // state SCO_STATE_ACTIVE_EXTERNAL and the mScoClients list not empty.
+                        if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
+                                || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
+                                        && !mScoClients.isEmpty())) {
                             if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
                                     && connectBluetoothScoAudioHelper(mBluetoothHeadset,
                                     mBluetoothHeadsetDevice, mScoAudioMode)) {
@@ -423,7 +430,9 @@
                             }
                         }
                         // Tear down SCO if disconnected from external
-                        clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+                        if (mScoAudioState == SCO_STATE_DEACTIVATING) {
+                            clearAllScoClients(0, false);
+                        }
                         mScoAudioState = SCO_STATE_INACTIVE;
                         Log.i(TAG, "Audio-path brought-down");
                     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 53f9ebc..1ed5cd8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2207,13 +2207,7 @@
         @Override
         public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
             enforceAccessPermission();
-            runOnServiceThread(new Runnable() {
-                @Override
-                public void run() {
-                    HdmiControlService.this.setHdmiCecVolumeControlEnabled(
-                            isHdmiCecVolumeControlEnabled);
-                }
-            });
+            HdmiControlService.this.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
         }
 
         @Override
@@ -3014,7 +3008,6 @@
     }
 
     void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
-        assertRunOnServiceThread();
         synchronized (mLock) {
             mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled;
 
@@ -3030,7 +3023,6 @@
     }
 
     boolean isHdmiCecVolumeControlEnabled() {
-        assertRunOnServiceThread();
         synchronized (mLock) {
             return mHdmiCecVolumeControlEnabled;
         }
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 8888108..c4f8441 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -41,9 +41,12 @@
 import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -174,6 +177,36 @@
             closeSessionInternal(token);
         }
 
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+
+            synchronized (LightsService.this) {
+                if (mVintfLights != null) {
+                    pw.println("Service: aidl (" + mVintfLights.get() + ")");
+                } else {
+                    pw.println("Service: hidl");
+                }
+
+                pw.println("Lights:");
+                for (int i = 0; i < mLightsById.size(); i++) {
+                    final LightImpl light = mLightsById.valueAt(i);
+                    pw.println(String.format("  Light id=%d ordinal=%d color=%08x",
+                            light.mHwLight.id, light.mHwLight.ordinal, light.getColor()));
+                }
+
+                pw.println("Session clients:");
+                for (Session session : mSessions) {
+                    pw.println("  Session token=" + session.mToken);
+                    for (int i = 0; i < session.mRequests.size(); i++) {
+                        pw.println(String.format("    Request id=%d color=%08x",
+                                session.mRequests.keyAt(i),
+                                session.mRequests.valueAt(i).getColor()));
+                    }
+                }
+            }
+        }
+
         private void closeSessionInternal(IBinder token) {
             synchronized (LightsService.this) {
                 final Session session = getSessionLocked(token);
diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING
new file mode 100644
index 0000000..f868ea0
--- /dev/null
+++ b/services/core/java/com/android/server/lights/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHardwareTestCases",
+      "options": [
+        {"include-filter": "com.android.hardware.lights"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.LargeTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.lights"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8f5b07c..bcfa6fc 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -314,7 +314,7 @@
         }
 
         void register(LockSettingsStrongAuth strongAuth) {
-            strongAuth.registerStrongAuthTracker(this.mStub);
+            strongAuth.registerStrongAuthTracker(getStub());
         }
     }
 
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 9e509f4..1a749b3 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -22,6 +22,7 @@
 import android.app.IProcessObserver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
@@ -43,6 +44,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -399,11 +401,12 @@
         public final UserHandle userHandle;
         private final int mTargetSdkVersion;
         private final boolean mIsPrivileged;
+        private final int mType;
 
         private IMediaProjectionCallback mCallback;
         private IBinder mToken;
         private IBinder.DeathRecipient mDeathEater;
-        private int mType;
+        private boolean mRestoreSystemAlertWindow;
 
         MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
                 boolean isPrivileged) {
@@ -494,6 +497,35 @@
                             "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
                     return;
                 }
+                if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        // We allow an app running a current screen capture session to use
+                        // SYSTEM_ALERT_WINDOW for the duration of the session, to enable
+                        // them to overlay their UX on top of what is being captured.
+                        // We only do this if the app requests the permission, and the appop
+                        // is in its default state (the user has neither explicitly allowed nor
+                        // disallowed it).
+                        final PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(
+                                packageName, PackageManager.GET_PERMISSIONS,
+                                UserHandle.getUserId(uid));
+                        if (ArrayUtils.contains(packageInfo.requestedPermissions,
+                                Manifest.permission.SYSTEM_ALERT_WINDOW)) {
+                            final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(
+                                    AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);
+                            if (currentMode == AppOpsManager.MODE_DEFAULT) {
+                                mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid,
+                                        packageName, AppOpsManager.MODE_ALLOWED);
+                                mRestoreSystemAlertWindow = true;
+                            }
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Package not found, aborting MediaProjection", e);
+                        return;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
                 startProjectionLocked(this);
             }
         }
@@ -507,6 +539,24 @@
                             + "pid=" + Binder.getCallingPid() + ")");
                     return;
                 }
+                if (mRestoreSystemAlertWindow) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        // Put the appop back how it was, unless it has been changed from what
+                        // we set it to.
+                        // Note that WindowManager takes care of removing any existing overlay
+                        // windows when we do this.
+                        final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(
+                                AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);
+                        if (currentMode == AppOpsManager.MODE_ALLOWED) {
+                            mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName,
+                                    AppOpsManager.MODE_DEFAULT);
+                        }
+                        mRestoreSystemAlertWindow = false;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
                 stopProjectionLocked(this);
                 mToken.unlinkToDeath(mDeathEater, 0);
                 mToken = null;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ecd56d3..5106be9 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -935,6 +935,21 @@
     }
 
     @Override
+    public void uninstallExistingPackage(VersionedPackage versionedPackage,
+            String callerPackageName, IntentSender statusReceiver, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES, null);
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+        if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+            mAppOps.checkPackage(callingUid, callerPackageName);
+        }
+
+        final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
+                statusReceiver, versionedPackage.getPackageName(), false, userId);
+        mPm.deleteExistingPackageAsUser(versionedPackage, adapter.getBinder(), userId);
+    }
+
+    @Override
     public void installExistingPackage(String packageName, int installFlags, int installReason,
             IntentSender statusReceiver, int userId, List<String> whiteListedPermissions) {
         mPm.installExistingPackageAsUser(packageName, userId, installFlags, installReason,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bff5e9..4d7fb4e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17982,8 +17982,46 @@
     }
 
     @Override
+    public void deleteExistingPackageAsUser(VersionedPackage versionedPackage,
+            final IPackageDeleteObserver2 observer, final int userId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.DELETE_PACKAGES, null);
+        Preconditions.checkNotNull(versionedPackage);
+        Preconditions.checkNotNull(observer);
+        final String packageName = versionedPackage.getPackageName();
+        final long versionCode = versionedPackage.getLongVersionCode();
+
+        int installedForUsersCount = 0;
+        synchronized (mLock) {
+            // Normalize package name to handle renamed packages and static libs
+            final String internalPkgName = resolveInternalPackageNameLPr(packageName, versionCode);
+            final PackageSetting ps = mSettings.getPackageLPr(internalPkgName);
+            if (ps != null) {
+                int[] installedUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
+                installedForUsersCount = installedUsers.length;
+            }
+        }
+
+        if (installedForUsersCount > 1) {
+            deletePackageVersionedInternal(versionedPackage, observer, userId, 0, true);
+        } else {
+            try {
+                observer.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+                        null);
+            } catch (RemoteException re) {
+            }
+        }
+    }
+
+    @Override
     public void deletePackageVersioned(VersionedPackage versionedPackage,
             final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {
+        deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);
+    }
+
+    private void deletePackageVersionedInternal(VersionedPackage versionedPackage,
+            final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
+            final boolean allowSilentUninstall) {
         final int callingUid = Binder.getCallingUid();
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.DELETE_PACKAGES, null);
@@ -18004,6 +18042,7 @@
 
         final int uid = Binder.getCallingUid();
         if (!isOrphaned(internalPackageName)
+                && !allowSilentUninstall
                 && !isCallerAllowedToSilentlyUninstall(uid, internalPackageName)) {
             mHandler.post(() -> {
                 try {
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 a1cc44a..7d49f78 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -421,7 +421,7 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mContext.getSystemService(PermissionControllerManager.class).dump(fd, pw, args);
+        mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c56fe2c..6e1bac1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1292,12 +1292,6 @@
         }
 
         if (stack != null && stack.topRunningActivity() == this) {
-            // carry over the PictureInPictureParams to the parent stack without calling
-            // TaskOrganizerController#dispatchTaskInfoChanged.
-            // this is to ensure the stack holding up-to-dated pinned stack information
-            // when activity is re-parented to enter pip mode, see also
-            // RootWindowContainer#moveActivityToPinnedStack
-            stack.mPictureInPictureParams.copyOnlySet(pictureInPictureArgs);
             // make ensure the TaskOrganizer still works after re-parenting
             if (firstWindowDrawn) {
                 stack.setHasBeenVisible(true);
@@ -7812,6 +7806,6 @@
 
     void setPictureInPictureParams(PictureInPictureParams p) {
         pictureInPictureArgs.copyOnlySet(p);
-        getTask().getRootTask().setPictureInPictureParams(p);
+        getTask().getRootTask().onPictureInPictureParamsChanged();
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 30021d1..c165242 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -710,8 +710,10 @@
         // Need to make sure windowing mode is supported. If we in the process of creating the stack
         // no need to resolve the windowing mode again as it is already resolved to the right mode.
         if (!creating) {
-            windowingMode = taskDisplayArea.validateWindowingMode(windowingMode,
-                    null /* ActivityRecord */, topTask, getActivityType());
+            if (!taskDisplayArea.isValidWindowingMode(windowingMode, null /* ActivityRecord */,
+                    topTask, getActivityType())) {
+                windowingMode = WINDOWING_MODE_UNDEFINED;
+            }
         }
         if (taskDisplayArea.getRootSplitScreenPrimaryTask() == this
                 && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 26a97be..8676e51 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -861,7 +861,7 @@
                         r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
                         r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
                         dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),
-                                r.assistToken));
+                        r.assistToken, r.createFixedRotationAdjustmentsIfNeeded()));
 
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
@@ -1473,6 +1473,7 @@
         mService.deferWindowLayout();
         try {
             stack.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            stack.setBounds(null);
             if (toDisplay.getDisplayId() != stack.getDisplayId()) {
                 stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */);
             } else {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 317bb43..d02be88 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -60,7 +60,37 @@
     private final IntArray mShowingTransientTypes = new IntArray();
 
     /** For resetting visibilities of insets sources. */
-    private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() { };
+    private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
+
+        @Override
+        public void notifyInsetsControlChanged() {
+            boolean hasLeash = false;
+            final InsetsSourceControl[] controls =
+                    mStateController.getControlsForDispatch(this);
+            if (controls == null) {
+                return;
+            }
+            for (InsetsSourceControl control : controls) {
+                final @InternalInsetsType int type = control.getType();
+                if (mShowingTransientTypes.indexOf(type) != -1) {
+                    // The visibilities of transient bars will be handled with animations.
+                    continue;
+                }
+                final SurfaceControl leash = control.getLeash();
+                if (leash != null) {
+                    hasLeash = true;
+
+                    // We use alpha to control the visibility here which aligns the logic at
+                    // SurfaceAnimator.createAnimationLeash
+                    mDisplayContent.getPendingTransaction().setAlpha(
+                            leash, InsetsState.getDefaultVisibility(type) ? 1f : 0f);
+                }
+            }
+            if (hasLeash) {
+                mDisplayContent.scheduleAnimation();
+            }
+        }
+    };
 
     private WindowState mFocusedWin;
     private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 4cd3180..513be7a 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -146,7 +146,10 @@
 
             if (mTmpParams.hasWindowingMode()
                     && mTmpParams.mWindowingMode != task.getStack().getWindowingMode()) {
-                task.getStack().setWindowingMode(mTmpParams.mWindowingMode);
+                final int activityType = activity != null
+                        ? activity.getActivityType() : task.getActivityType();
+                task.getStack().setWindowingMode(task.getDisplayArea().validateWindowingMode(
+                        mTmpParams.mWindowingMode, activity, task, activityType));
             }
 
             if (mTmpParams.mBounds.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 640f50c..5efbc3e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2170,7 +2170,7 @@
             final boolean singleActivity = task.getChildCount() == 1;
             final ActivityStack stack;
             if (singleActivity) {
-                stack = r.getRootTask();
+                stack = (ActivityStack) task;
             } else {
                 // In the case of multiple activities, we will create a new task for it and then
                 // move the PIP activity into the task.
@@ -2183,6 +2183,11 @@
                 // up-to-dated pinned stack information on this newly created stack.
                 r.reparent(stack, MAX_VALUE, reason);
             }
+            if (stack.getParent() != taskDisplayArea) {
+                // stack is nested, but pinned tasks need to be direct children of their
+                // display area, so reparent.
+                stack.reparent(taskDisplayArea, true /* onTop */);
+            }
             stack.setWindowingMode(WINDOWING_MODE_PINNED);
 
             // Reset the state that indicates it can enter PiP while pausing after we've moved it
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index c79cb04..3d305e4 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -45,11 +45,22 @@
     private final float[] mFloat9 = new float[9];
     private final int mOldRotation;
     private final int mNewRotation;
+    /* If the seamless rotator is used to rotate part of the hierarchy, then provide a transform
+     * hint based on the display orientation if the entire display was rotated. When the display
+     * orientation matches the hierarchy orientation, the fixed transform hint will be removed.
+     * This will prevent allocating different buffer sizes by the graphic producers when the
+     * orientation of a layer changes.
+     */
+    private final boolean mApplyFixedTransformHint;
+    private final int mFixedTransformHint;
 
-    public SeamlessRotator(@Rotation int oldRotation, @Rotation int newRotation, DisplayInfo info) {
+
+    public SeamlessRotator(@Rotation int oldRotation, @Rotation int newRotation, DisplayInfo info,
+            boolean applyFixedTransformationHint) {
         mOldRotation = oldRotation;
         mNewRotation = newRotation;
-
+        mApplyFixedTransformHint = applyFixedTransformationHint;
+        mFixedTransformHint = oldRotation;
         final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
         final int pH = flipped ? info.logicalWidth : info.logicalHeight;
         final int pW = flipped ? info.logicalHeight : info.logicalWidth;
@@ -70,6 +81,9 @@
         final float[] winSurfacePos = {win.mLastSurfacePosition.x, win.mLastSurfacePosition.y};
         mTransform.mapPoints(winSurfacePos);
         transaction.setPosition(win.getSurfaceControl(), winSurfacePos[0], winSurfacePos[1]);
+        if (mApplyFixedTransformHint) {
+            transaction.setFixedTransformHint(win.mSurfaceControl, mFixedTransformHint);
+        }
     }
 
     /**
@@ -109,6 +123,9 @@
         mTransform.reset();
         t.setMatrix(win.mSurfaceControl, mTransform, mFloat9);
         t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y);
+        if (mApplyFixedTransformHint) {
+            t.unsetFixedTransformHint(win.mSurfaceControl);
+        }
     }
 
     public void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4845da1..b9e6513 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -107,7 +107,6 @@
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.AppGlobals;
-import android.app.PictureInPictureParams;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
@@ -488,12 +487,6 @@
     boolean mTaskAppearedSent;
 
     /**
-     * Last Picture-in-Picture params applicable to the task. Updated when the app
-     * enters Picture-in-Picture or when setPictureInPictureParams is called.
-     */
-    PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build();
-
-    /**
      * This task was created by the task organizer which has the following implementations.
      * <ul>
      *     <lis>The task won't be removed when it is empty. Removal has to be an explicit request
@@ -3571,10 +3564,11 @@
         info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
         info.topActivityType = top.getActivityType();
 
-        if (mPictureInPictureParams.empty()) {
+        ActivityRecord rootActivity = top.getRootActivity();
+        if (rootActivity == null || rootActivity.pictureInPictureArgs.empty()) {
             info.pictureInPictureParams = null;
         } else {
-            info.pictureInPictureParams = mPictureInPictureParams;
+            info.pictureInPictureParams = rootActivity.pictureInPictureArgs;
         }
         info.topActivityInfo = mReuseActivitiesReport.top != null
                 ? mReuseActivitiesReport.top.info
@@ -4490,8 +4484,7 @@
         updateShadowsRadius(hasFocus, getPendingTransaction());
     }
 
-    void setPictureInPictureParams(PictureInPictureParams p) {
-        mPictureInPictureParams.copyOnlySet(p);
+    void onPictureInPictureParamsChanged() {
         if (isOrganized()) {
             mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, true /* force */);
         }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 0b64451..1225714 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -21,7 +21,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -1389,16 +1388,16 @@
     }
 
     /**
-     * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+     * Check if the requested windowing-mode is appropriate for the specified task and/or activity
      * on this display.
      *
      * @param windowingMode The windowing-mode to validate.
      * @param r The {@link ActivityRecord} to check against.
      * @param task The {@link Task} to check against.
      * @param activityType An activity type.
-     * @return The provided windowingMode or the closest valid mode which is appropriate.
+     * @return {@code true} if windowingMode is valid, {@code false} otherwise.
      */
-    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+    boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
             int activityType) {
         // Make sure the windowing mode we are trying to use makes sense for what is supported.
         boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
@@ -1418,24 +1417,35 @@
             }
         }
 
+        return windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                        supportsFreeform, supportsPip, activityType);
+    }
+
+    /**
+     * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+     * on this display.
+     *
+     * @param windowingMode The windowing-mode to validate.
+     * @param r The {@link ActivityRecord} to check against.
+     * @param task The {@link Task} to check against.
+     * @param activityType An activity type.
+     * @return The provided windowingMode or the closest valid mode which is appropriate.
+     */
+    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+            int activityType) {
         final boolean inSplitScreenMode = isSplitScreenModeActivated();
-        if (!inSplitScreenMode
-                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+        if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
             // Switch to the display's windowing mode if we are not in split-screen mode and we are
             // trying to launch in split-screen secondary.
             windowingMode = WINDOWING_MODE_UNDEFINED;
-        } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_UNDEFINED)
-                && supportsSplitScreen) {
+        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_UNDEFINED) {
             windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
         }
-
-        if (windowingMode != WINDOWING_MODE_UNDEFINED
-                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
-                supportsFreeform, supportsPip, activityType)) {
-            return windowingMode;
+        if (!isValidWindowingMode(windowingMode, r, task, activityType)) {
+            return WINDOWING_MODE_UNDEFINED;
         }
-        return WINDOWING_MODE_UNDEFINED;
+        return windowingMode;
     }
 
     boolean isTopStack(ActivityStack stack) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8a47109..2dbc6b5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2263,7 +2263,7 @@
             win.mRelayoutCalled = true;
             win.mInRelayout = true;
 
-            win.mViewVisibility = viewVisibility;
+            win.setViewVisibility(viewVisibility);
             ProtoLog.i(WM_DEBUG_SCREEN_ON,
                     "Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,
                             viewVisibility, new RuntimeException().fillInStackTrace());
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f6473fd..e925ce5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -741,7 +741,8 @@
             if (mControllableInsetProvider != null) {
                 mControllableInsetProvider.startSeamlessRotation();
             }
-            mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
+            mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo(),
+                    false /* applyFixedTransformationHint */);
             mPendingSeamlessRotate.unrotate(transaction, this);
             getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
                     true /* seamlesslyRotated */);
@@ -5688,6 +5689,17 @@
         return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow();
     }
 
+    void setViewVisibility(int viewVisibility) {
+        mViewVisibility = viewVisibility;
+        // The viewVisibility is set to GONE with a client request to relayout. If this occurs and
+        // there's a blast sync transaction waiting, finishDrawing will never be called since the
+        // client will not render when visibility is GONE. Therefore, call finishDrawing here to
+        // prevent system server from blocking on a window that will not draw.
+        if (viewVisibility == View.GONE && mUsingBLASTSyncTransaction) {
+            finishDrawing(null);
+        }
+    }
+
     SurfaceControl getClientViewRootSurface() {
         return mWinAnimator.getClientViewRootSurface();
     }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 148264a..768f89e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -19,6 +19,7 @@
 import static android.os.Process.INVALID_UID;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
@@ -40,6 +41,7 @@
 
 import android.annotation.CallSuper;
 import android.app.IWindowToken;
+import android.app.servertransaction.FixedRotationAdjustmentsItem;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Debug;
@@ -47,6 +49,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
@@ -141,7 +144,7 @@
             mRotatedOverrideConfiguration = rotatedConfig;
             // This will use unrotate as rotate, so the new and old rotation are inverted.
             mRotator = new SeamlessRotator(rotatedDisplayInfo.rotation, currentRotation,
-                    rotatedDisplayInfo);
+                    rotatedDisplayInfo, true /* applyFixedTransformationHint */);
         }
 
         /**
@@ -529,6 +532,7 @@
         mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
                 insetsState, new Configuration(config), mDisplayContent.getRotation());
         onConfigurationChanged(getParent().getConfiguration());
+        notifyFixedRotationTransform(true /* enabled */);
     }
 
     /**
@@ -546,6 +550,7 @@
         mFixedRotationTransformState = fixedRotationState;
         fixedRotationState.mAssociatedTokens.add(this);
         onConfigurationChanged(getParent().getConfiguration());
+        notifyFixedRotationTransform(true /* enabled */);
     }
 
     void finishFixedRotationTransform() {
@@ -578,9 +583,52 @@
         // The state is cleared at the end, because it is used to indicate that other windows can
         // use seamless rotation when applying rotation to display.
         for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
-            state.mAssociatedTokens.get(i).mFixedRotationTransformState = null;
+            state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState();
         }
+        cleanUpFixedRotationTransformState();
+    }
+
+    private void cleanUpFixedRotationTransformState() {
         mFixedRotationTransformState = null;
+        notifyFixedRotationTransform(false /* enabled */);
+    }
+
+    /** Notifies application side to enable or disable the rotation adjustment of display info. */
+    private void notifyFixedRotationTransform(boolean enabled) {
+        FixedRotationAdjustments adjustments = null;
+        // A token may contain windows of the same processes or different processes. The list is
+        // used to avoid sending the same adjustments to a process multiple times.
+        ArrayList<WindowProcessController> notifiedProcesses = null;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            final WindowProcessController app;
+            if (w.mAttrs.type == TYPE_APPLICATION_STARTING) {
+                // Use the host activity because starting window is controlled by window manager.
+                final ActivityRecord r = asActivityRecord();
+                if (r == null) {
+                    continue;
+                }
+                app = r.app;
+            } else {
+                app = mWmService.mAtmService.mProcessMap.getProcess(w.mSession.mPid);
+            }
+            if (app == null || !app.hasThread()) {
+                continue;
+            }
+            if (notifiedProcesses == null) {
+                notifiedProcesses = new ArrayList<>(2);
+                adjustments = enabled ? createFixedRotationAdjustmentsIfNeeded() : null;
+            } else if (notifiedProcesses.contains(app)) {
+                continue;
+            }
+            notifiedProcesses.add(app);
+            try {
+                mWmService.mAtmService.getLifecycleManager().scheduleTransaction(
+                        app.getThread(), FixedRotationAdjustmentsItem.obtain(token, adjustments));
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to schedule DisplayAdjustmentsItem to " + app, e);
+            }
+        }
     }
 
     /** Restores the changes that applies to this container. */
@@ -590,6 +638,7 @@
             // The window may be detached or detaching.
             return;
         }
+        notifyFixedRotationTransform(false /* enabled */);
         final int originalRotation = getWindowConfiguration().getRotation();
         onConfigurationChanged(parent.getConfiguration());
         onCancelFixedRotationTransform(originalRotation);
@@ -603,6 +652,14 @@
     void onCancelFixedRotationTransform(int originalDisplayRotation) {
     }
 
+    FixedRotationAdjustments createFixedRotationAdjustmentsIfNeeded() {
+        if (!isFixedRotationTransforming()) {
+            return null;
+        }
+        return new FixedRotationAdjustments(mFixedRotationTransformState.mDisplayInfo.rotation,
+                mFixedRotationTransformState.mDisplayInfo.displayCutout);
+    }
+
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
         super.resolveOverrideConfiguration(newParentConfig);
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
new file mode 100644
index 0000000..8646a53
--- /dev/null
+++ b/services/core/jni/OWNERS
@@ -0,0 +1,13 @@
+# Display
+per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
+
+# Haptics
+per-file com_android_server_VibratorService.cpp = michaelwr@google.com
+
+# Input
+per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com, svv@google.com
+
+# Power
+per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com
+per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com
+
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index da716ea..c687184 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -30,6 +30,7 @@
 import android.os.IBinder;
 import android.os.ServiceManager;
 import android.provider.Settings;
+import android.provider.Telephony;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -84,6 +85,7 @@
         result.removeAll(getSystemLauncherPackages());
         result.removeAll(getAccessibilityServices());
         result.removeAll(getInputMethodPackages());
+        result.remove(Telephony.Sms.getDefaultSmsPackage(mContext));
         result.remove(getSettingsPackageName());
 
         final String[] unsuspendablePackages =
diff --git a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
index 5d849c1..2be3f1e8 100644
--- a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
@@ -19,6 +19,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import android.app.usage.UsageEvents;
 import android.content.res.Configuration;
@@ -26,9 +27,12 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Field;
 import java.util.Locale;
 
 @RunWith(AndroidJUnit4.class)
@@ -191,4 +195,27 @@
         assertTrue(intervalStats.events.size() > NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE);
         assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
     }
+
+    // All fields in this list are defined in IntervalStats and persisted - please ensure they're
+    // defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto
+    private static final String[] INTERVALSTATS_PERSISTED_FIELDS = {"beginTime", "endTime",
+            "mStringCache", "majorVersion", "minorVersion", "interactiveTracker",
+            "nonInteractiveTracker", "keyguardShownTracker", "keyguardHiddenTracker",
+            "packageStats", "configurations", "activeConfiguration", "events"};
+    // All fields in this list are defined in IntervalStats but not persisted
+    private static final String[] INTERVALSTATS_IGNORED_FIELDS = {"lastTimeSaved",
+            "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG"};
+
+    @Test
+    public void testIntervalStatsFieldsAreKnown() {
+        final IntervalStats stats = new IntervalStats();
+        final Field[] fields = stats.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            if (!(ArrayUtils.contains(INTERVALSTATS_PERSISTED_FIELDS, field.getName())
+                    || ArrayUtils.contains(INTERVALSTATS_IGNORED_FIELDS, field.getName()))) {
+                fail("Found an unknown field: " + field.getName() + ". Please correctly update "
+                        + "either INTERVALSTATS_PERSISTED_FIELDS or INTERVALSTATS_IGNORED_FIELDS.");
+            }
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index fc256b0..702d9d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -53,6 +53,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.pm.PackageManagerService;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -161,6 +162,12 @@
         mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
     }
 
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+    }
+
     @Test
     public void testSuspendedByAdminPackage() {
         // GIVEN the package we're about to launch is currently suspended
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index 851b052..d7eedd9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.NonNull;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -265,4 +266,15 @@
     public SurfaceControl.Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction setFixedTransformHint(SurfaceControl sc,
+            @Surface.Rotation int transformHint) {
+        return this;
+    }
+
+    @Override
+    public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
+        return this;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 2ea58a0..fdc5c7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -836,7 +836,7 @@
         spyOn(record);
         doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean());
 
-        record.getRootTask().setHasBeenVisible(true);
+        record.getTask().setHasBeenVisible(true);
         return record;
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsIdleService.java b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
index 4468871..3163820 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
@@ -27,6 +27,8 @@
 
 import com.android.server.LocalServices;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * JobService used to do any work for UsageStats while the device is idle.
  */
@@ -36,6 +38,11 @@
      * Base job ID for the pruning job - must be unique within the system server uid.
      */
     private static final int PRUNE_JOB_ID = 546357475;
+    /**
+     * Job ID for the update mappings job - must be unique within the system server uid.
+     * Incrementing PRUNE_JOB_ID by 21475 (MAX_USER_ID) to ensure there is no overlap in job ids.
+     */
+    private static final int UPDATE_MAPPINGS_JOB_ID = 546378950;
 
     private static final String USER_ID_KEY = "user_id";
 
@@ -51,35 +58,65 @@
                 .setPersisted(true)
                 .build();
 
+        scheduleJobInternal(context, pruneJob, userJobId);
+    }
+
+    static void scheduleUpdateMappingsJob(Context context) {
+        final ComponentName component = new ComponentName(context.getPackageName(),
+                UsageStatsIdleService.class.getName());
+        final JobInfo updateMappingsJob = new JobInfo.Builder(UPDATE_MAPPINGS_JOB_ID, component)
+                .setPersisted(true)
+                .setMinimumLatency(TimeUnit.DAYS.toMillis(1))
+                .setOverrideDeadline(TimeUnit.DAYS.toMillis(2))
+                .build();
+
+        scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_ID);
+    }
+
+    private static void scheduleJobInternal(Context context, JobInfo pruneJob, int jobId) {
         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
-        final JobInfo pendingPruneJob = jobScheduler.getPendingJob(userJobId);
+        final JobInfo pendingPruneJob = jobScheduler.getPendingJob(jobId);
         // only schedule a new prune job if one doesn't exist already for this user
         if (!pruneJob.equals(pendingPruneJob)) {
-            jobScheduler.cancel(userJobId); // cancel any previously scheduled prune job
+            jobScheduler.cancel(jobId); // cancel any previously scheduled prune job
             jobScheduler.schedule(pruneJob);
         }
-
     }
 
     static void cancelJob(Context context, int userId) {
-        final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user
+        cancelJobInternal(context, PRUNE_JOB_ID + userId);
+    }
+
+    static void cancelUpdateMappingsJob(Context context) {
+        cancelJobInternal(context, UPDATE_MAPPINGS_JOB_ID);
+    }
+
+    private static void cancelJobInternal(Context context, int jobId) {
         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
-        jobScheduler.cancel(userJobId);
+        if (jobScheduler != null) {
+            jobScheduler.cancel(jobId);
+        }
     }
 
     @Override
     public boolean onStartJob(JobParameters params) {
         final PersistableBundle bundle = params.getExtras();
         final int userId = bundle.getInt(USER_ID_KEY, -1);
-        if (userId == -1) {
+        if (userId == -1 && params.getJobId() != UPDATE_MAPPINGS_JOB_ID) {
             return false;
         }
 
         AsyncTask.execute(() -> {
             final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService(
                     UsageStatsManagerInternal.class);
-            final boolean pruned = usageStatsManagerInternal.pruneUninstalledPackagesData(userId);
-            jobFinished(params, !pruned); // reschedule if data was not pruned
+            if (params.getJobId() == UPDATE_MAPPINGS_JOB_ID) {
+                final boolean jobFinished = usageStatsManagerInternal.updatePackageMappingsData();
+                jobFinished(params, !jobFinished); // reschedule if data was not updated
+            } else {
+                final boolean jobFinished =
+                        usageStatsManagerInternal.pruneUninstalledPackagesData(userId);
+                jobFinished(params, !jobFinished); // reschedule if data was not pruned
+            }
         });
         return true;
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 5b5d57b..b59556f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -332,6 +332,11 @@
     private void onUserUnlocked(int userId) {
         // fetch the installed packages outside the lock so it doesn't block package manager.
         final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
+        // delay updating of package mappings for user 0 since their data is not likely to be stale.
+        // this also makes it less likely for restored data to be erased on unexpected reboots.
+        if (userId == UserHandle.USER_SYSTEM) {
+            UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
+        }
         synchronized (mLock) {
             // Create a user unlocked event to report
             final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
@@ -543,8 +548,8 @@
      * Initializes the given user's usage stats service - this should ideally only be called once,
      * when the user is initially unlocked.
      */
-    private void initializeUserUsageStatsServiceLocked(int userId,
-            long currentTimeMillis, HashMap<String, Long> installedPackages) {
+    private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis,
+            HashMap<String, Long> installedPackages) {
         final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
                 "usagestats");
         final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId,
@@ -931,6 +936,7 @@
         }
         // Cancel any scheduled jobs for this user since the user is being removed.
         UsageStatsIdleService.cancelJob(getContext(), userId);
+        UsageStatsIdleService.cancelUpdateMappingsJob(getContext());
     }
 
     /**
@@ -980,6 +986,26 @@
     /**
      * Called by the Binder stub.
      */
+    private boolean updatePackageMappingsData() {
+        // fetch the installed packages outside the lock so it doesn't block package manager.
+        final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
+        synchronized (mLock) {
+            if (!mUserUnlockedStates.get(UserHandle.USER_SYSTEM)) {
+                return false; // user is no longer unlocked
+            }
+
+            final UserUsageStatsService userService = mUserState.get(UserHandle.USER_SYSTEM);
+            if (userService == null) {
+                return false; // user was stopped or removed
+            }
+
+            return userService.updatePackageMappingsLocked(installedPkgs);
+        }
+    }
+
+    /**
+     * Called by the Binder stub.
+     */
     List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
             boolean obfuscateInstantApps) {
         synchronized (mLock) {
@@ -1847,7 +1873,7 @@
             final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
             if (!hasPermissions(callingPackage,
                     Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
-                    && (dpmInternal != null && !dpmInternal.isActiveSupervisionApp(callingUid))) {
+                    && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
                 throw new SecurityException("Caller must be the active supervision app or "
                         + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
             }
@@ -1874,7 +1900,7 @@
             final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
             if (!hasPermissions(callingPackage,
                     Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
-                    && (dpmInternal != null && !dpmInternal.isActiveSupervisionApp(callingUid))) {
+                    && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
                 throw new SecurityException("Caller must be the active supervision app or "
                         + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
             }
@@ -2137,6 +2163,9 @@
                 }
 
                 // Check to ensure that only user 0's data is b/r for now
+                // Note: if backup and restore is enabled for users other than the system user, the
+                // #onUserUnlocked logic, specifically when the update mappings job is scheduled via
+                // UsageStatsIdleService.scheduleUpdateMappingsJob, will have to be updated.
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user);
                     if (userStats == null) {
@@ -2229,6 +2258,11 @@
         public boolean pruneUninstalledPackagesData(int userId) {
             return UsageStatsService.this.pruneUninstalledPackagesData(userId);
         }
+
+        @Override
+        public boolean updatePackageMappingsData() {
+            return UsageStatsService.this.updatePackageMappingsData();
+        }
     }
 
     private class MyPackageMonitor extends PackageMonitor {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 4e75b73..26de11a 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -40,6 +40,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -181,19 +182,27 @@
 
     private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) {
         mDatabase.readMappingsLocked();
-        updatePackageMappingsLocked(installedPackages);
+        // Package mappings for the system user are updated after 24 hours via a job scheduled by
+        // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally,
+        // this makes user service initialization a little quicker on subsequent boots.
+        if (mUserId != UserHandle.USER_SYSTEM) {
+            updatePackageMappingsLocked(installedPackages);
+        }
     }
 
     /**
-     * Queries Job Scheduler for any pending data prune jobs and if any exist, it updates the
-     * package mappings in memory by removing those tokens.
+     * Compares the package mappings on disk with the ones currently installed and removes the
+     * mappings for those packages that have been uninstalled.
      * This will only happen once per device boot, when the user is unlocked for the first time.
+     * If the user is the system user (user 0), this is delayed to ensure data for packages
+     * that were restored isn't removed before the restore is complete.
      *
      * @param installedPackages map of installed packages (package_name:package_install_time)
+     * @return {@code true} on a successful mappings update, {@code false} otherwise.
      */
-    private void updatePackageMappingsLocked(HashMap<String, Long> installedPackages) {
+    boolean updatePackageMappingsLocked(HashMap<String, Long> installedPackages) {
         if (ArrayUtils.isEmpty(installedPackages)) {
-            return;
+            return true;
         }
 
         final long timeNow = System.currentTimeMillis();
@@ -206,7 +215,7 @@
             }
         }
         if (removedPackages.isEmpty()) {
-            return;
+            return true;
         }
 
         // remove packages in the mappings that are no longer installed and persist to disk
@@ -217,7 +226,9 @@
             mDatabase.writeMappingsLocked();
         } catch (Exception e) {
             Slog.w(TAG, "Unable to write updated package mappings file on service initialization.");
+            return false;
         }
+        return true;
     }
 
     boolean pruneUninstalledPackagesData() {
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index ff70f8b..29286e8 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -240,5 +240,5 @@
      * two.
      * Type: int
      */
-    static final String PROPERTY_MAX_ACTIVE_MODEMS = "ro.telephony.max.active.modems";
+    static final String PROPERTY_MAX_ACTIVE_MODEMS = "telephony.active_modems.max_count";
 }
diff --git a/test-mock/api/lint-baseline.txt b/test-mock/api/lint-baseline.txt
index c6ba3f5..1411824 100644
--- a/test-mock/api/lint-baseline.txt
+++ b/test-mock/api/lint-baseline.txt
@@ -21,10 +21,6 @@
     Missing nullability on parameter `url` in method `getStreamTypes`
 MissingNullability: android.test.mock.MockContentProvider#getStreamTypes(android.net.Uri, String) parameter #1:
     Missing nullability on parameter `mimeTypeFilter` in method `getStreamTypes`
-MissingNullability: android.test.mock.MockContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean) parameter #0:
-    Missing nullability on parameter `uri` in method `notifyChange`
-MissingNullability: android.test.mock.MockContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean) parameter #1:
-    Missing nullability on parameter `observer` in method `notifyChange`
 MissingNullability: android.test.mock.MockContext#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection) parameter #0:
     Missing nullability on parameter `service` in method `bindIsolatedService`
 MissingNullability: android.test.mock.MockContext#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection) parameter #2:
@@ -39,6 +35,10 @@
     Missing nullability on parameter `executor` in method `bindService`
 MissingNullability: android.test.mock.MockContext#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection) parameter #3:
     Missing nullability on parameter `conn` in method `bindService`
+MissingNullability: android.test.mock.MockContext#createWindowContext(int, android.os.Bundle) parameter #1:
+    Missing nullability on parameter `options` in method `createWindowContext`
+MissingNullability: android.test.mock.MockContext#getDisplay():
+    Missing nullability on method `getDisplay` return
 MissingNullability: android.test.mock.MockContext#getMainExecutor():
     Missing nullability on method `getMainExecutor` return
 MissingNullability: android.test.mock.MockContext#sendOrderedBroadcast(android.content.Intent, String, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle) parameter #0:
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 23098ec..529d03c 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -547,6 +547,16 @@
 
     @Test
     public void testApplyTransportModeTransform() throws Exception {
+        verifyApplyTransportModeTransformCommon(false);
+    }
+
+    @Test
+    public void testApplyTransportModeTransformReleasedSpi() throws Exception {
+        verifyApplyTransportModeTransformCommon(true);
+    }
+
+    public void verifyApplyTransportModeTransformCommon(
+                boolean closeSpiBeforeApply) throws Exception {
         IpSecConfig ipSecConfig = new IpSecConfig();
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
         addAuthAndCryptToIpSecConfig(ipSecConfig);
@@ -554,6 +564,39 @@
         IpSecTransformResponse createTransformResp =
                 mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
 
+        if (closeSpiBeforeApply) {
+            mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+        }
+
+        Socket socket = new Socket();
+        socket.bind(null);
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
+
+        int resourceId = createTransformResp.resourceId;
+        mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId);
+
+        verify(mMockNetd)
+                .ipSecApplyTransportModeTransform(
+                        eq(pfd),
+                        eq(mUid),
+                        eq(IpSecManager.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI));
+    }
+
+    @Test
+    public void testApplyTransportModeTransformWithClosedSpi() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+
+        // Close SPI record
+        mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+
         Socket socket = new Socket();
         socket.bind(null);
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
@@ -660,6 +703,15 @@
 
     @Test
     public void testApplyTunnelModeTransform() throws Exception {
+        verifyApplyTunnelModeTransformCommon(false);
+    }
+
+    @Test
+    public void testApplyTunnelModeTransformReleasedSpi() throws Exception {
+        verifyApplyTunnelModeTransformCommon(true);
+    }
+
+    public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply) throws Exception {
         IpSecConfig ipSecConfig = new IpSecConfig();
         ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
@@ -670,6 +722,49 @@
         IpSecTunnelInterfaceResponse createTunnelResp =
                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
 
+        if (closeSpiBeforeApply) {
+            mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+        }
+
+        int transformResourceId = createTransformResp.resourceId;
+        int tunnelResourceId = createTunnelResp.resourceId;
+        mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
+                transformResourceId, "blessedPackage");
+
+        for (int selAddrFamily : ADDRESS_FAMILIES) {
+            verify(mMockNetd)
+                    .ipSecUpdateSecurityPolicy(
+                            eq(mUid),
+                            eq(selAddrFamily),
+                            eq(IpSecManager.DIRECTION_OUT),
+                            anyString(),
+                            anyString(),
+                            eq(TEST_SPI),
+                            anyInt(), // iKey/oKey
+                            anyInt(), // mask
+                            eq(tunnelResourceId));
+        }
+
+        ipSecConfig.setXfrmInterfaceId(tunnelResourceId);
+        verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
+    }
+
+
+    @Test
+    public void testApplyTunnelModeTransformWithClosedSpi() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+        IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+
+        // Close SPI record
+        mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
         mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
diff --git a/tools/validatekeymaps/OWNERS b/tools/validatekeymaps/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/tools/validatekeymaps/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 6dd6864..30bc6a7 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -529,6 +529,161 @@
      * {@hide}
      */
     public final static int UNSPECIFIED = -1;
+
+    /**
+     * 2.4 GHz band first channel number
+     * @hide
+     */
+    public static final int BAND_24_GHZ_FIRST_CH_NUM = 1;
+    /**
+     * 2.4 GHz band last channel number
+     * @hide
+     */
+    public static final int BAND_24_GHZ_LAST_CH_NUM = 14;
+    /**
+     * 2.4 GHz band frequency of first channel in MHz
+     * @hide
+     */
+    public static final int BAND_24_GHZ_START_FREQ_MHZ = 2412;
+    /**
+     * 2.4 GHz band frequency of last channel in MHz
+     * @hide
+     */
+    public static final int BAND_24_GHZ_END_FREQ_MHZ = 2484;
+
+    /**
+     * 5 GHz band first channel number
+     * @hide
+     */
+    public static final int BAND_5_GHZ_FIRST_CH_NUM = 32;
+    /**
+     * 5 GHz band last channel number
+     * @hide
+     */
+    public static final int BAND_5_GHZ_LAST_CH_NUM = 173;
+    /**
+     * 5 GHz band frequency of first channel in MHz
+     * @hide
+     */
+    public static final int BAND_5_GHZ_START_FREQ_MHZ = 5160;
+    /**
+     * 5 GHz band frequency of last channel in MHz
+     * @hide
+     */
+    public static final int BAND_5_GHZ_END_FREQ_MHZ = 5865;
+
+    /**
+     * 6 GHz band first channel number
+     * @hide
+     */
+    public static final int BAND_6_GHZ_FIRST_CH_NUM = 1;
+    /**
+     * 6 GHz band last channel number
+     * @hide
+     */
+    public static final int BAND_6_GHZ_LAST_CH_NUM = 233;
+    /**
+     * 6 GHz band frequency of first channel in MHz
+     * @hide
+     */
+    public static final int BAND_6_GHZ_START_FREQ_MHZ = 5945;
+    /**
+     * 6 GHz band frequency of last channel in MHz
+     * @hide
+     */
+    public static final int BAND_6_GHZ_END_FREQ_MHZ = 7105;
+
+    /**
+     * Utility function to check if a frequency within 2.4 GHz band
+     * @param freqMhz frequency in MHz
+     * @return true if within 2.4GHz, false otherwise
+     *
+     * @hide
+     */
+    public static boolean is24GHz(int freqMhz) {
+        return freqMhz >= BAND_24_GHZ_START_FREQ_MHZ && freqMhz <= BAND_24_GHZ_END_FREQ_MHZ;
+    }
+
+    /**
+     * Utility function to check if a frequency within 5 GHz band
+     * @param freqMhz frequency in MHz
+     * @return true if within 5GHz, false otherwise
+     *
+     * @hide
+     */
+    public static boolean is5GHz(int freqMhz) {
+        return freqMhz >=  BAND_5_GHZ_START_FREQ_MHZ && freqMhz <= BAND_5_GHZ_END_FREQ_MHZ;
+    }
+
+    /**
+     * Utility function to check if a frequency within 6 GHz band
+     * @param freqMhz
+     * @return true if within 6GHz, false otherwise
+     *
+     * @hide
+     */
+    public static boolean is6GHz(int freqMhz) {
+        return freqMhz >= BAND_6_GHZ_START_FREQ_MHZ && freqMhz <= BAND_6_GHZ_END_FREQ_MHZ;
+    }
+
+    /**
+     * Utility function to convert channel number/band to frequency in MHz
+     * @param channel number to convert
+     * @param band of channel to convert
+     * @return center frequency in Mhz of the channel, {@link UNSPECIFIED} if no match
+     *
+     * @hide
+     */
+    public static int convertChannelToFrequencyMhz(int channel, @WifiScanner.WifiBand int band) {
+        if (band == WifiScanner.WIFI_BAND_24_GHZ) {
+            // Special case
+            if (channel == 14) {
+                return 2484;
+            } else if (channel >= BAND_24_GHZ_FIRST_CH_NUM && channel <= BAND_24_GHZ_LAST_CH_NUM) {
+                return ((channel - BAND_24_GHZ_FIRST_CH_NUM) * 5) + BAND_24_GHZ_START_FREQ_MHZ;
+            } else {
+                return UNSPECIFIED;
+            }
+        }
+        if (band == WifiScanner.WIFI_BAND_5_GHZ) {
+            if (channel >= BAND_5_GHZ_FIRST_CH_NUM && channel <= BAND_5_GHZ_LAST_CH_NUM) {
+                return ((channel - BAND_5_GHZ_FIRST_CH_NUM) * 5) + BAND_5_GHZ_START_FREQ_MHZ;
+            } else {
+                return UNSPECIFIED;
+            }
+        }
+        if (band == WifiScanner.WIFI_BAND_6_GHZ) {
+            if (channel >= BAND_6_GHZ_FIRST_CH_NUM && channel <= BAND_6_GHZ_LAST_CH_NUM) {
+                return ((channel - BAND_6_GHZ_FIRST_CH_NUM) * 5) + BAND_6_GHZ_START_FREQ_MHZ;
+            } else {
+                return UNSPECIFIED;
+            }
+        }
+        return UNSPECIFIED;
+    }
+
+    /**
+     * Utility function to convert frequency in MHz to channel number
+     * @param freqMhz frequency in MHz
+     * @return channel number associated with given frequency, {@link UNSPECIFIED} if no match
+     *
+     * @hide
+     */
+    public static int convertFrequencyMhzToChannel(int freqMhz) {
+        // Special case
+        if (freqMhz == 2484) {
+            return 14;
+        } else if (is24GHz(freqMhz)) {
+            return (freqMhz - BAND_24_GHZ_START_FREQ_MHZ) / 5 + BAND_24_GHZ_FIRST_CH_NUM;
+        } else if (is5GHz(freqMhz)) {
+            return ((freqMhz - BAND_5_GHZ_START_FREQ_MHZ) / 5) + BAND_5_GHZ_FIRST_CH_NUM;
+        } else if (is6GHz(freqMhz)) {
+            return ((freqMhz - BAND_6_GHZ_START_FREQ_MHZ) / 5) + BAND_6_GHZ_FIRST_CH_NUM;
+        }
+
+        return UNSPECIFIED;
+    }
+
     /**
      * @hide
      */
@@ -538,14 +693,6 @@
 
     /**
      * @hide
-     * TODO: makes real freq boundaries
-     */
-    public static boolean is24GHz(int freq) {
-        return freq > 2400 && freq < 2500;
-    }
-
-    /**
-     * @hide
      */
     public boolean is5GHz() {
         return ScanResult.is5GHz(frequency);
@@ -560,21 +707,6 @@
 
     /**
      * @hide
-     * TODO: makes real freq boundaries
-     */
-    public static boolean is5GHz(int freq) {
-        return freq > 4900 && freq < 5900;
-    }
-
-    /**
-     * @hide
-     */
-    public static boolean is6GHz(int freq) {
-        return freq > 5925 && freq < 7125;
-    }
-
-    /**
-     * @hide
      */
     public boolean is60GHz() {
         return ScanResult.is60GHz(frequency);
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
index 6cb8324..5516f43 100644
--- a/wifi/tests/src/android/net/wifi/ScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -46,6 +46,68 @@
             ScanResult.WIFI_STANDARD_11AC;
 
     /**
+     * Frequency to channel map. This include some frequencies used outside the US.
+     * Representing it using a vector (instead of map) for simplification.
+     */
+    private static final int[] FREQUENCY_TO_CHANNEL_MAP = {
+            2412, WifiScanner.WIFI_BAND_24_GHZ, 1,
+            2417, WifiScanner.WIFI_BAND_24_GHZ, 2,
+            2422, WifiScanner.WIFI_BAND_24_GHZ, 3,
+            2427, WifiScanner.WIFI_BAND_24_GHZ, 4,
+            2432, WifiScanner.WIFI_BAND_24_GHZ, 5,
+            2437, WifiScanner.WIFI_BAND_24_GHZ, 6,
+            2442, WifiScanner.WIFI_BAND_24_GHZ, 7,
+            2447, WifiScanner.WIFI_BAND_24_GHZ, 8,
+            2452, WifiScanner.WIFI_BAND_24_GHZ, 9,
+            2457, WifiScanner.WIFI_BAND_24_GHZ, 10,
+            2462, WifiScanner.WIFI_BAND_24_GHZ, 11,
+            /* 12, 13 are only legitimate outside the US. */
+            2467, WifiScanner.WIFI_BAND_24_GHZ, 12,
+            2472, WifiScanner.WIFI_BAND_24_GHZ, 13,
+            /* 14 is for Japan, DSSS and CCK only. */
+            2484, WifiScanner.WIFI_BAND_24_GHZ, 14,
+            /* 34 valid in Japan. */
+            5170, WifiScanner.WIFI_BAND_5_GHZ, 34,
+            5180, WifiScanner.WIFI_BAND_5_GHZ, 36,
+            5190, WifiScanner.WIFI_BAND_5_GHZ, 38,
+            5200, WifiScanner.WIFI_BAND_5_GHZ, 40,
+            5210, WifiScanner.WIFI_BAND_5_GHZ, 42,
+            5220, WifiScanner.WIFI_BAND_5_GHZ, 44,
+            5230, WifiScanner.WIFI_BAND_5_GHZ, 46,
+            5240, WifiScanner.WIFI_BAND_5_GHZ, 48,
+            5260, WifiScanner.WIFI_BAND_5_GHZ, 52,
+            5280, WifiScanner.WIFI_BAND_5_GHZ, 56,
+            5300, WifiScanner.WIFI_BAND_5_GHZ, 60,
+            5320, WifiScanner.WIFI_BAND_5_GHZ, 64,
+            5500, WifiScanner.WIFI_BAND_5_GHZ, 100,
+            5520, WifiScanner.WIFI_BAND_5_GHZ, 104,
+            5540, WifiScanner.WIFI_BAND_5_GHZ, 108,
+            5560, WifiScanner.WIFI_BAND_5_GHZ, 112,
+            5580, WifiScanner.WIFI_BAND_5_GHZ, 116,
+            /* 120, 124, 128 valid in Europe/Japan. */
+            5600, WifiScanner.WIFI_BAND_5_GHZ, 120,
+            5620, WifiScanner.WIFI_BAND_5_GHZ, 124,
+            5640, WifiScanner.WIFI_BAND_5_GHZ, 128,
+            /* 132+ valid in US. */
+            5660, WifiScanner.WIFI_BAND_5_GHZ, 132,
+            5680, WifiScanner.WIFI_BAND_5_GHZ, 136,
+            5700, WifiScanner.WIFI_BAND_5_GHZ, 140,
+            /* 144 is supported by a subset of WiFi chips. */
+            5720, WifiScanner.WIFI_BAND_5_GHZ, 144,
+            5745, WifiScanner.WIFI_BAND_5_GHZ, 149,
+            5765, WifiScanner.WIFI_BAND_5_GHZ, 153,
+            5785, WifiScanner.WIFI_BAND_5_GHZ, 157,
+            5805, WifiScanner.WIFI_BAND_5_GHZ, 161,
+            5825, WifiScanner.WIFI_BAND_5_GHZ, 165,
+            5845, WifiScanner.WIFI_BAND_5_GHZ, 169,
+            5865, WifiScanner.WIFI_BAND_5_GHZ, 173,
+            /* Now some 6GHz channels */
+            5945, WifiScanner.WIFI_BAND_6_GHZ, 1,
+            5960, WifiScanner.WIFI_BAND_6_GHZ, 4,
+            6100, WifiScanner.WIFI_BAND_6_GHZ, 32
+    };
+
+    /**
      * Setup before tests.
      */
     @Before
@@ -184,6 +246,25 @@
     }
 
     /**
+     * verify frequency to channel conversion for all possible frequencies.
+     */
+    @Test
+    public void convertFrequencyToChannel() throws Exception {
+        for (int i = 0; i < FREQUENCY_TO_CHANNEL_MAP.length; i += 3) {
+            assertEquals(FREQUENCY_TO_CHANNEL_MAP[i + 2],
+                    ScanResult.convertFrequencyMhzToChannel(FREQUENCY_TO_CHANNEL_MAP[i]));
+        }
+    }
+
+    /**
+     * Verify frequency to channel conversion failed for an invalid frequency.
+     */
+    @Test
+    public void convertFrequencyToChannelWithInvalidFreq() throws Exception {
+        assertEquals(-1, ScanResult.convertFrequencyMhzToChannel(8000));
+    }
+
+    /**
      * Write the provided {@link ScanResult} to a parcel and deserialize it.
      */
     private static ScanResult parcelReadWrite(ScanResult writeResult) throws Exception {