Merge "Do not wake up all displays" into tm-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 528be3c..a09f39f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -348,7 +348,8 @@
             new SparseArray<>();
 
     private AppStateTrackerImpl mAppStateTracker;
-    private boolean mAppStandbyParole;
+    @VisibleForTesting
+    boolean mAppStandbyParole;
 
     /**
      * A container to keep rolling window history of previous times when an alarm was sent to
@@ -1891,6 +1892,9 @@
                         (AppStateTrackerImpl) LocalServices.getService(AppStateTracker.class);
                 mAppStateTracker.addListener(mForceAppStandbyListener);
 
+                final BatteryManager bm = getContext().getSystemService(BatteryManager.class);
+                mAppStandbyParole = bm.isCharging();
+
                 mClockReceiver.scheduleTimeTickEvent();
                 mClockReceiver.scheduleDateChangedEvent();
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 3ba4df8..dfd019d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -265,14 +265,6 @@
                     )
             );
 
-    /**
-     * This array essentially stores the state of mActiveServices array.
-     * The ith index stores the job present on the ith JobServiceContext.
-     * We manipulate this array until we arrive at what jobs should be running on
-     * what JobServiceContext.
-     */
-    JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
-
     private final ArraySet<ContextAssignment> mRecycledChanged = new ArraySet<>();
     private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
     private final ArraySet<ContextAssignment> mRecycledPreferredUidOnly = new ArraySet<>();
@@ -580,7 +572,6 @@
         final List<JobServiceContext> activeServices = mActiveServices;
 
         // To avoid GC churn, we recycle the arrays.
-        JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
         final ArraySet<ContextAssignment> changed = mRecycledChanged;
         final ArraySet<ContextAssignment> idle = mRecycledIdle;
         final ArraySet<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly;
@@ -638,7 +629,7 @@
             idle.add(assignment);
         }
         if (DEBUG) {
-            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
+            Slog.d(TAG, printAssignments("running jobs initial", stoppable, preferredUidOnly));
         }
 
         mWorkCountTracker.onCountDone();
@@ -747,7 +738,8 @@
             }
         }
         if (DEBUG) {
-            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
+            Slog.d(TAG, printAssignments("running jobs final",
+                    stoppable, preferredUidOnly, changed));
 
             Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
         }
@@ -1283,13 +1275,26 @@
         return s.toString();
     }
 
-    private static String printContextIdToJobMap(JobStatus[] map, String initial) {
-        StringBuilder s = new StringBuilder(initial + ": ");
-        for (int i=0; i<map.length; i++) {
-            s.append("(")
-                    .append(map[i] == null? -1: map[i].getJobId())
-                    .append(map[i] == null? -1: map[i].getUid())
-                    .append(")" );
+    private static String printAssignments(String header, ArraySet<ContextAssignment>... list) {
+        final StringBuilder s = new StringBuilder(header + ": ");
+        for (int l = 0; l < list.length; ++l) {
+            ArraySet<ContextAssignment> assignments = list[l];
+            for (int c = 0; c < assignments.size(); ++c) {
+                final ContextAssignment assignment = assignments.valueAt(c);
+                final JobStatus job = assignment.newJob == null
+                        ? assignment.context.getRunningJobLocked() : assignment.newJob;
+
+                if (l > 0 || c > 0) {
+                    s.append(" ");
+                }
+                s.append("(").append(assignment.context.getId()).append("=");
+                if (job == null) {
+                    s.append("nothing");
+                } else {
+                    s.append(job.getJobId()).append("/").append(job.getUid());
+                }
+                s.append(")");
+            }
         }
         return s.toString();
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index f91472b..daf1ee1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -182,8 +182,10 @@
         if (earliestQueue != null) {
             final JobStatus job = earliestQueue.next();
             // Change the front of the queue if we've pulled pullLimit jobs from the current head
+            // or we're dealing with test jobs
             // or the current head has no more jobs to provide.
             if (++mPullCount >= pullLimit
+                    || (job != null && earliestQueue.peekNextOverrideState() != job.overrideState)
                     || earliestQueue.peekNextTimestamp() == AppJobQueue.NO_NEXT_TIMESTAMP) {
                 mOrderedQueues.poll();
                 if (earliestQueue.peekNextTimestamp() != AppJobQueue.NO_NEXT_TIMESTAMP) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 7db358c..80f3fea 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -623,7 +623,8 @@
      * @param elapsedRealtime current time
      * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
      * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
-     * @return The index whose values the app's used time exceeds (in both arrays)
+     * @return The index whose values the app's used time exceeds (in both arrays) or {@code -1} to
+     *         indicate that the app has never been used.
      */
     int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
             long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
@@ -631,14 +632,13 @@
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, false);
         // If we don't have any state for the app, assume never used
-        if (appUsageHistory == null) return screenTimeThresholds.length - 1;
+        if (appUsageHistory == null || appUsageHistory.lastUsedElapsedTime < 0
+                || appUsageHistory.lastUsedScreenTime < 0) {
+            return -1;
+        }
 
-        long screenOnDelta = appUsageHistory.lastUsedScreenTime >= 0
-                ? getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime
-                : Long.MAX_VALUE;
-        long elapsedDelta = appUsageHistory.lastUsedElapsedTime >= 0
-                ? getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime
-                : Long.MAX_VALUE;
+        long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
+        long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
 
         if (DEBUG) Slog.d(TAG, packageName
                 + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 0839faa..1e4ecc2 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -898,11 +898,9 @@
                     }
                 }
 
-                final long elapsedLastUsedByUserTimeDelta = app.lastUsedByUserElapsedTime >= 0
-                        ? elapsedTimeAdjusted - app.lastUsedByUserElapsedTime
-                        : Long.MAX_VALUE;
-                if (app.lastRestrictAttemptElapsedTime > app.lastUsedByUserElapsedTime
-                        && elapsedLastUsedByUserTimeDelta
+                if (app.lastUsedByUserElapsedTime >= 0
+                        && app.lastRestrictAttemptElapsedTime > app.lastUsedByUserElapsedTime
+                        && elapsedTimeAdjusted - app.lastUsedByUserElapsedTime
                         >= mInjector.getAutoRestrictedBucketDelayMs()) {
                     newBucket = STANDBY_BUCKET_RESTRICTED;
                     reason = app.lastRestrictReason;
@@ -974,7 +972,7 @@
             long elapsedRealtime) {
         int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
                 elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
-        return THRESHOLD_BUCKETS[bucketIndex];
+        return bucketIndex >= 0 ? THRESHOLD_BUCKETS[bucketIndex] : STANDBY_BUCKET_NEVER;
     }
 
     private void notifyBatteryStats(String packageName, int userId, boolean idle) {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b28db82..e5e13a8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1108,7 +1108,7 @@
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
-    method @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 9c8639d..95ac799 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -630,8 +630,9 @@
     /**
      * Delete uid from the ActivityManagerService PendingStartActivityUids list.
      * @param uid uid
+     * @param nowElapsed starting time of updateOomAdj
      */
-    public abstract void deletePendingTopUid(int uid);
+    public abstract void deletePendingTopUid(int uid, long nowElapsed);
 
     /**
      * Is the uid in ActivityManagerService PendingStartActivityUids list?
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index ac979c4..3cc2b56 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -381,6 +381,10 @@
      * Force the global system in or out of touch mode. This can be used if your
      * instrumentation relies on the UI being in one more or the other when it starts.
      *
+     * <p><b>Note:</b> Starting from Android {@link Build.VERSION_CODES#TIRAMISU}, this method
+     * will only take effect if the instrumentation was sourced from a process with
+     * {@code MODIFY_TOUCH_MODE_STATE} internal permission granted (shell already have it).
+     *
      * @param inTouch Set to true to be in touch mode, false to be in focus mode.
      */
     public void setInTouchMode(boolean inTouch) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 82e5ebf..df6627c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6197,7 +6197,7 @@
      * organization-owned managed profile.
      *
      * <p>The caller must hold the
-     * {@link android.Manifest.permission#SEND_LOST_MODE_LOCATION_UPDATES} permission.
+     * {@link android.Manifest.permission#TRIGGER_LOST_MODE} permission.
      *
      * <p> Not for use by third-party applications.
      *
@@ -6207,7 +6207,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES)
+    @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE)
     public void sendLostModeLocationUpdate(@NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         throwIfParentInstance("sendLostModeLocationUpdate");
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index a1983ca..914b321 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -469,6 +469,10 @@
         /**
          * Called when the top activity is changed.
          *
+         * <p>Note: When there are no activities running on the virtual display, the
+         * {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
+         * should be cleared when {@link #onDisplayEmpty(int)} is called.
+         *
          * @param displayId The display ID on which the activity change happened.
          * @param topActivity The component name of the top activity.
          */
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 450e09a..88ef054 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -419,8 +419,8 @@
     public @interface FileLocation{}
 
     /**
-     * The installer did not call SessionParams#setPackageSource(int) to specify the package
-     * source.
+     * The installer did not call {@link PackageInstaller.SessionParams#setPackageSource(int)} to
+     * specify the package source.
      */
     public static final int PACKAGE_SOURCE_UNSPECIFIED = 0;
 
@@ -444,8 +444,8 @@
 
     /**
      * Code indicating that the package being installed comes from a file that was downloaded to
-     * the device by the user. For use in place of PACKAGE_SOURCE_LOCAL_FILE when the installer
-     * knows the package was downloaded.
+     * the device by the user. For use in place of {@link #PACKAGE_SOURCE_LOCAL_FILE} when the
+     * installer knows the package was downloaded.
      */
     public static final int PACKAGE_SOURCE_DOWNLOADED_FILE = 4;
 
@@ -1984,7 +1984,13 @@
         }
 
         /**
-         * Sets the apk package installation source.
+         * Optionally indicate the package source of the app being installed. This is
+         * informational and may be used as a signal by the system.
+         *
+         * An installer should specify {@link #PACKAGE_SOURCE_OTHER} if no other package source
+         * constant adequately reflects the source for this session.
+         *
+         * The default value is {@link #PACKAGE_SOURCE_UNSPECIFIED}.
          */
         public void setPackageSource(@PackageSourceType int packageSource) {
             this.packageSource = packageSource;
@@ -2991,7 +2997,8 @@
         }
 
         /**
-         * Gets the apk package installation source.
+         * Get the package source that was set in
+         * {@link PackageInstaller.SessionParams#setPackageSource(int)}.
          */
         public @PackageSourceType int getPackageSource() {
             return packageSource;
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index a13579d..406281d 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -401,7 +401,7 @@
     public void relayout(WindowManager.LayoutParams attrs,
             WindowlessWindowManager.ResizeCompleteCallback callback) {
         mViewRoot.setLayoutParams(attrs, false);
-        mViewRoot.setReportNextDraw();
+        mViewRoot.setReportNextDraw(true /* syncBuffer */);
         mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);
     }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 4138556..c04b096 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -50,6 +50,7 @@
 import com.android.internal.view.SurfaceCallbackHelper;
 
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Consumer;
 
@@ -203,8 +204,6 @@
 
     private int mSurfaceFlags = SurfaceControl.HIDDEN;
 
-    private int mPendingReportDraws;
-
     /**
      * Transaction that should be used from the render thread. This transaction is only thread safe
      * with other calls directly from the render thread.
@@ -212,11 +211,6 @@
     private final SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
 
     /**
-     * Used on the main thread to set the transaction that will be synced with the main window.
-     */
-    private final Transaction mSyncTransaction = new Transaction();
-
-    /**
      * Transaction that should be used whe
      * {@link HardwareRenderer.FrameDrawingCallback#onFrameDraw} is invoked. All
      * frame callbacks can use the same transaction since they will be thread safe
@@ -391,31 +385,12 @@
         }
     }
 
-    private void performDrawFinished(@Nullable Transaction t) {
-        if (t != null) {
-            mSyncTransaction.merge(t);
+    private void performDrawFinished() {
+        mDrawFinished = true;
+        if (mAttachedToWindow) {
+            mParent.requestTransparentRegion(SurfaceView.this);
+            invalidate();
         }
-
-        if (mPendingReportDraws > 0) {
-            mDrawFinished = true;
-            if (mAttachedToWindow) {
-                mParent.requestTransparentRegion(SurfaceView.this);
-                notifyDrawFinished();
-                invalidate();
-            }
-        } else {
-            Log.e(TAG, System.identityHashCode(this) + "finished drawing"
-                    + " but no pending report draw (extra call"
-                    + " to draw completion runnable?)");
-        }
-    }
-
-    void notifyDrawFinished() {
-        ViewRootImpl viewRoot = getViewRootImpl();
-        if (viewRoot != null) {
-            viewRoot.pendingDrawFinished(mSyncTransaction);
-        }
-        mPendingReportDraws--;
     }
 
     @Override
@@ -438,10 +413,6 @@
             mGlobalListenersAdded = false;
         }
 
-        while (mPendingReportDraws > 0) {
-            notifyDrawFinished();
-        }
-
         mRequestedVisible = false;
 
         updateSurface();
@@ -993,10 +964,17 @@
                     return;
                 }
 
-                final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
-                        translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction);
                 final boolean redrawNeeded = sizeChanged || creating || hintChanged
                         || (mVisible && !mDrawFinished);
+                final TransactionCallback transactionCallback =
+                        redrawNeeded ? new TransactionCallback() : null;
+                if (redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInSync()) {
+                    mBlastBufferQueue.syncNextTransaction(
+                            false /* acquireSingleBuffer */,
+                            transactionCallback::onTransactionReady);
+                }
+                final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
+                        translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction);
 
                 try {
                     SurfaceHolder.Callback[] callbacks = null;
@@ -1015,9 +993,7 @@
                             mIsCreating = true;
                             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                     + "visibleChanged -- surfaceCreated");
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
+                            callbacks = getSurfaceCallbacks();
                             for (SurfaceHolder.Callback c : callbacks) {
                                 c.surfaceCreated(mSurfaceHolder);
                             }
@@ -1035,32 +1011,7 @@
                             }
                         }
                         if (redrawNeeded) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "surfaceRedrawNeeded");
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
-
-                            final boolean wasRelayoutRequested = viewRoot.wasRelayoutRequested();
-                            if (wasRelayoutRequested && (mBlastBufferQueue != null)) {
-                                mBlastBufferQueue.syncNextTransaction(
-                                        false /* acquireSingleBuffer */,
-                                        this::onDrawFinished);
-                            }
-                            mPendingReportDraws++;
-                            viewRoot.drawPending();
-                            SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
-                                if (mBlastBufferQueue != null) {
-                                    mBlastBufferQueue.stopContinuousSyncTransaction();
-                                }
-                                // If relayout was requested, then a callback from BBQ will
-                                // be invoked with the sync transaction. onDrawFinished will be
-                                // called in there
-                                if (!wasRelayoutRequested) {
-                                    onDrawFinished(null);
-                                }
-                            });
-                            sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+                            redrawNeeded(callbacks, transactionCallback);
                         }
                     }
                 } finally {
@@ -1079,6 +1030,64 @@
         }
     }
 
+    private void redrawNeeded(SurfaceHolder.Callback[] callbacks,
+            @Nullable TransactionCallback transactionCallback) {
+        if (DEBUG) {
+            Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+        }
+        final SurfaceHolder.Callback[] capturedCallbacks =
+                callbacks == null ? getSurfaceCallbacks() : callbacks;
+
+        ViewRootImpl viewRoot = getViewRootImpl();
+        boolean isVriSync = viewRoot.addToSync(syncBufferCallback ->
+                redrawNeededAsync(capturedCallbacks, () -> {
+                    if (mBlastBufferQueue != null) {
+                        mBlastBufferQueue.stopContinuousSyncTransaction();
+                    }
+
+                    Transaction t = null;
+                    if (transactionCallback != null && mBlastBufferQueue != null) {
+                        t = transactionCallback.waitForTransaction();
+                    }
+                    // If relayout was requested, then a callback from BBQ will
+                    // be invoked with the sync transaction. onDrawFinished will be
+                    // called in there
+                    syncBufferCallback.onBufferReady(t);
+                    onDrawFinished();
+                }));
+
+        // If isVriSync, then everything was setup in the addToSync.
+        if (isVriSync) {
+            return;
+        }
+
+        redrawNeededAsync(capturedCallbacks, this::onDrawFinished);
+    }
+
+    private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
+            Runnable callbacksCollected) {
+        SurfaceCallbackHelper sch = new SurfaceCallbackHelper(callbacksCollected);
+        sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+    }
+
+    private static class TransactionCallback {
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+        private Transaction mTransaction;
+
+        Transaction waitForTransaction() {
+            try {
+                mCountDownLatch.await();
+            } catch (InterruptedException e) {
+            }
+            return mTransaction;
+        }
+
+        void onTransactionReady(Transaction t) {
+            mTransaction = t;
+            mCountDownLatch.countDown();
+        }
+    }
+
     /**
      * Copy the Surface from the SurfaceControl or the blast adapter.
      *
@@ -1189,13 +1198,13 @@
         mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
     }
 
-    private void onDrawFinished(@Nullable Transaction t) {
+    private void onDrawFinished() {
         if (DEBUG) {
             Log.i(TAG, System.identityHashCode(this) + " "
                     + "finishedDrawing");
         }
 
-        runOnUiThread(() -> performDrawFinished(t));
+        runOnUiThread(this::performDrawFinished);
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2613c1a..a3e6945 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -587,11 +587,16 @@
 
 
     boolean mReportNextDraw;
+
     /**
-     * Set whether the draw should use blast sync. This is in case the draw is canceled,
-     * but will be rescheduled. We still want the next draw to be sync.
+     * Set whether the draw should send the buffer to system server. When set to true, VRI will
+     * create a sync transaction with BBQ and send the resulting buffer to system server. If false,
+     * VRI will not try to sync a buffer in BBQ, but still report when a draw occurred.
      */
-    boolean mNextDrawUseBlastSync;
+    private boolean mSyncBuffer = false;
+
+    int mSyncSeqId = 0;
+    int mLastSyncSeqId = 0;
 
     boolean mFullRedrawNeeded;
     boolean mNewSurfaceNeeded;
@@ -812,6 +817,10 @@
         return mHandwritingInitiator;
     }
 
+    private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
+    private int mLastSyncId = -1;
+    private SurfaceSyncer.SyncBufferCallback mSyncBufferCallback;
+
     /**
      * Keeps track of the last frame number that was attempted to draw. Should only be accessed on
      * the RenderThread.
@@ -849,8 +858,6 @@
      * integer back over relayout.
      */
     private Bundle mRelayoutBundle = new Bundle();
-    private int mSyncSeqId = 0;
-    private int mLastSyncSeqId = 0;
 
     private String mTag = TAG;
 
@@ -2654,7 +2661,6 @@
     private void performTraversals() {
         // cache mView since it is used so much below...
         final View host = mView;
-
         if (DBG) {
             System.out.println("======================================");
             System.out.println("performTraversals");
@@ -2899,8 +2905,6 @@
                 mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance);
             }
         }
-        final boolean wasReportNextDraw = mReportNextDraw;
-        boolean useBlastSync = mNextDrawUseBlastSync;
 
         if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
                 || mForceNextWindowRelayout) {
@@ -2939,9 +2943,7 @@
                         Log.d(mTag, "Relayout called with blastSync");
                     }
                     reportNextDraw();
-                    if (isHardwareEnabled()) {
-                        useBlastSync = true;
-                    }
+                    mSyncBuffer = true;
                 }
 
                 final boolean surfaceControlChanged =
@@ -3177,7 +3179,7 @@
             // done to achieve a more hermetic fix for S, but it's entirely
             // possible that checking the most recent value is actually more
             // correct here.
-            if (!mStopped || wasReportNextDraw) {
+            if (!mStopped || mReportNextDraw) {
                 if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
                         || dispatchApplyInsets || updatedConfiguration) {
                     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
@@ -3246,7 +3248,7 @@
             prepareSurfaces();
         }
 
-        final boolean didLayout = layoutRequested && (!mStopped || wasReportNextDraw);
+        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
         boolean triggerGlobalLayoutListener = didLayout
                 || mAttachInfo.mRecomputeGlobalAttributes;
         if (didLayout) {
@@ -3439,51 +3441,37 @@
 
         mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
 
-        // Remember if we must report the next draw.
         if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
             reportNextDraw();
         }
 
-        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
-        if (mBLASTDrawConsumer != null) {
-            useBlastSync = true;
+        boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+        if (!cancelAndRedraw) {
+            createSyncIfNeeded();
         }
 
-        if (!cancelDraw) {
+        if (!isViewVisible) {
+            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+                for (int i = 0; i < mPendingTransitions.size(); ++i) {
+                    mPendingTransitions.get(i).endChangingAnimations();
+                }
+                mPendingTransitions.clear();
+            }
+
+            if (mSyncBufferCallback != null) {
+                mSyncBufferCallback.onBufferReady(null);
+            }
+        } else if (cancelAndRedraw) {
+            // Try again
+            scheduleTraversals();
+        } else {
             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
                     mPendingTransitions.get(i).startChangingAnimations();
                 }
                 mPendingTransitions.clear();
             }
-            performDraw(useBlastSync);
-            mNextDrawUseBlastSync = false;
-        } else {
-            if (isViewVisible) {
-                // Try again
-                mNextDrawUseBlastSync = useBlastSync;
-                scheduleTraversals();
-            } else {
-                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
-                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
-                        mPendingTransitions.get(i).endChangingAnimations();
-                    }
-                    mPendingTransitions.clear();
-                }
-
-                // We may never draw since it's not visible. Report back that we're finished
-                // drawing.
-                if (!wasReportNextDraw && mReportNextDraw) {
-                    mReportNextDraw = false;
-                    pendingDrawFinished();
-                }
-
-                // Make sure the consumer is not waiting if the view root was just made invisible.
-                if (mBLASTDrawConsumer != null) {
-                    mBLASTDrawConsumer.accept(null);
-                    mBLASTDrawConsumer = null;
-                }
-            }
+            performDraw();
         }
 
         if (mAttachInfo.mContentCaptureEvents != null) {
@@ -3492,6 +3480,48 @@
 
         mIsInTraversal = false;
         mRelayoutRequested = false;
+
+        if (!cancelAndRedraw) {
+            mReportNextDraw = false;
+            mSyncBufferCallback = null;
+            mSyncBuffer = false;
+            if (mLastSyncId != -1) {
+                mSurfaceSyncer.markSyncReady(mLastSyncId);
+                mLastSyncId = -1;
+            }
+        }
+    }
+
+    private void createSyncIfNeeded() {
+        // Started a sync already.
+        if (mLastSyncId != -1) {
+            return;
+        }
+
+        Consumer<Transaction> syncConsumer = null;
+        final int seqId = mSyncSeqId;
+
+        if (mBLASTDrawConsumer != null) {
+            syncConsumer = mBLASTDrawConsumer;
+            mBLASTDrawConsumer = null;
+        } else if (mReportNextDraw) {
+            syncConsumer = transaction -> {
+                mSurfaceChangedTransaction.merge(transaction);
+                reportDrawFinished(seqId);
+            };
+        }
+
+        if (syncConsumer != null) {
+            final Consumer<Transaction> capturedSyncConsumer = syncConsumer;
+            mLastSyncId = mSurfaceSyncer.setupSync(transaction -> {
+                // Callback will be invoked on executor thread so post to main thread.
+                mHandler.postAtFrontOfQueue(() -> capturedSyncConsumer.accept(transaction));
+            });
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Setup new sync id=" + mLastSyncId);
+            }
+            mSurfaceSyncer.addToSync(mLastSyncId, mSyncTarget);
+        }
     }
 
     private void notifyContentCatpureEvents() {
@@ -4105,57 +4135,13 @@
         }
     }
 
-    /**
-     * A count of the number of calls to pendingDrawFinished we
-     * require to notify the WM drawing is complete.
-     */
-    int mDrawsNeededToReport = 0;
-
-    /**
-     * Delay notifying WM of draw finished until
-     * a balanced call to pendingDrawFinished.
-     */
-    void drawPending() {
-        mDrawsNeededToReport++;
-    }
-
-    void pendingDrawFinished(Transaction t) {
-        if (mDrawsNeededToReport == 0) {
-            throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls");
-        }
-
-        if (t != null) {
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "Merging transaction into main window transaction");
-            }
-            mSurfaceChangedTransaction.merge(t);
-        }
-
-        mDrawsNeededToReport--;
-        if (mDrawsNeededToReport == 0) {
-            reportDrawFinished();
-        } else if (DEBUG_BLAST) {
-            Log.d(mTag, "pendingDrawFinished. Waiting on draw reported mDrawsNeededToReport="
-                    + mDrawsNeededToReport);
-        }
-    }
-
-    void pendingDrawFinished() {
-        pendingDrawFinished(null);
-    }
-
-    private void postDrawFinished() {
-        mHandler.sendEmptyMessage(MSG_DRAW_FINISHED);
-    }
-
-    private void reportDrawFinished() {
+    private void reportDrawFinished(int seqId) {
         if (DEBUG_BLAST) {
-            Log.d(mTag, "reportDrawFinished");
+            Log.d(mTag, "reportDrawFinished " + Debug.getCallers(5));
         }
-        mDrawsNeededToReport = 0;
 
         try {
-            mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction, Integer.MAX_VALUE);
+            mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction, seqId);
         } catch (RemoteException e) {
             Log.e(mTag, "Unable to report draw finished", e);
             mSurfaceChangedTransaction.apply();
@@ -4171,6 +4157,19 @@
         return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
     }
 
+    boolean addToSync(SurfaceSyncer.SyncTarget syncable) {
+        if (mLastSyncId == -1) {
+            return false;
+        }
+        mSurfaceSyncer.addToSync(mLastSyncId, syncable);
+        return true;
+    }
+
+
+    public boolean isInSync() {
+        return mLastSyncId != -1;
+    }
+
     private void addFrameCommitCallbackIfNeeded() {
         if (!isHardwareEnabled()) {
             return;
@@ -4201,188 +4200,82 @@
         });
     }
 
-    private HardwareRenderer.FrameCommitCallback createFrameCommitCallbackForSync(
-            boolean useBlastSync, boolean reportNextDraw, Consumer<Transaction> blastSyncConsumer) {
-        return didProduceBuffer -> {
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "Received frameCommittedCallback "
-                        + " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum
-                        + " didProduceBuffer=" + didProduceBuffer);
-            }
-
-            // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
-            // draw attempt. The next transaction and transaction complete callback were only set
-            // for the current draw attempt.
-            final Transaction pendingTransactions;
-            if (!didProduceBuffer) {
-                mBlastBufferQueue.syncNextTransaction(null);
-                // Get the transactions that were sent to mergeWithNextTransaction since the
-                // frame didn't draw on this vsync. It's possible the frame will draw later, but
-                // it's better to not be sync than to block on a frame that may never come.
-                pendingTransactions = mBlastBufferQueue.gatherPendingTransactions(
-                        mRtLastAttemptedDrawFrameNum);
-                if (!useBlastSync && !reportNextDraw) {
-                    pendingTransactions.apply();
-                }
-            } else {
-                pendingTransactions = null;
-            }
-            // Post at front of queue so the buffer can be processed immediately and allow RT
-            // to continue processing new buffers. If RT tries to process buffers before the sync
-            // buffer is applied, the new buffers will not get acquired and could result in a
-            // deadlock. UI thread would wait on RT, but RT would be blocked waiting for a free
-            // buffer.
-            mHandler.postAtFrontOfQueue(() -> {
-                if (!didProduceBuffer && useBlastSync) {
-                    mSurfaceChangedTransaction.merge(pendingTransactions);
-                    if (blastSyncConsumer != null) {
-                        blastSyncConsumer.accept(mSurfaceChangedTransaction);
-                    }
-                }
-
-                // This is to ensure pendingDrawFinished is only called exactly one time per draw
-                // attempt when reportNextDraw is true. Since, we sometimes create a sync
-                // transaction callback, the callback will handle calling pendingDrawFinished.
-                // However, there are cases where the transaction callback may not be called.
-                // 1. If useBlastSync is false, then we know that a sync transaction callback was
-                // not created so we won't invoke pendingDrawFinished there.
-                // 2. If the draw didn't produce a frame, didProduceBuffer == false, then we know
-                // the sync transaction callback will not be invoked even if one was set up.
-                if (reportNextDraw && (!didProduceBuffer || !useBlastSync)) {
-                    pendingDrawFinished();
-                }
-            });
-
-        };
-    }
-
     @Nullable
-    private FrameDrawingCallback createFrameDrawingCallbackIfNeeded(boolean useBlastSync,
-            boolean reportNextDraw) {
+    private void registerFrameDrawingCallbackForBlur() {
         if (!isHardwareEnabled()) {
-            return null;
+            return;
         }
         final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
         final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
 
-        if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw && !mHasPendingTransactions) {
-            return null;
-        }
-
-        final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer;
-        mBLASTDrawConsumer = null;
-
-        if (DEBUG_BLAST) {
-            Log.d(mTag, "Creating frameDrawingCallback"
-                    + " nextDrawUseBlastSync=" + useBlastSync
-                    + " reportNextDraw=" + reportNextDraw
-                    + " hasBlurUpdates=" + hasBlurUpdates
-                    + " hasBlastSyncConsumer=" + (blastSyncConsumer != null)
-                    + " mHasPendingTransactions=" + mHasPendingTransactions);
+        if (!needsCallbackForBlur) {
+            return;
         }
 
         final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
-                needsCallbackForBlur ?  mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
-        final boolean hasPendingTransactions = mHasPendingTransactions;
-        mHasPendingTransactions = false;
+                mBlurRegionAggregator.getBlurRegionsCopyForRT();
 
         // The callback will run on the render thread.
-        return new FrameDrawingCallback() {
+        registerRtFrameCallback((frame) -> mBlurRegionAggregator
+                .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates));
+    }
+
+    private void registerCallbackForPendingTransactions() {
+        registerRtFrameCallback(new FrameDrawingCallback() {
+            @Override
+            public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) {
+                if ((syncResult
+                        & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
+                    mBlastBufferQueue.applyPendingTransactions(frame);
+                    return null;
+                }
+
+                return didProduceBuffer -> {
+                    if (!didProduceBuffer) {
+                        mBlastBufferQueue.applyPendingTransactions(frame);
+                    }
+                };
+
+            }
+
             @Override
             public void onFrameDraw(long frame) {
             }
-
-            @Override
-            public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) {
-                if (DEBUG_BLAST) {
-                    Log.d(mTag,
-                            "Received frameDrawingCallback syncResult=" + syncResult + " frameNum="
-                                    + frame + ".");
-                }
-
-                mRtLastAttemptedDrawFrameNum = frame;
-
-                if (needsCallbackForBlur) {
-                    mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame,
-                            blurRegionsForFrame, hasBlurUpdates);
-                }
-
-                if (mBlastBufferQueue == null) {
-                    return null;
-                }
-
-                if (!useBlastSync && !reportNextDraw && !hasPendingTransactions) {
-                    return null;
-                }
-
-                // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or
-                // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up
-                // any blast sync or commit callback, and the code should directly call
-                // pendingDrawFinished.
-                if ((syncResult
-                        & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
-                    if (reportNextDraw) {
-                        mHandler.postAtFrontOfQueue(() -> pendingDrawFinished());
-                    }
-                    return null;
-                }
-
-                if (DEBUG_BLAST) {
-                    Log.d(mTag, "Setting up sync and frameCommitCallback");
-                }
-
-                if (useBlastSync) {
-                    // Frame callbacks will always occur after submitting draw requests and before
-                    // the draw actually occurs. This will ensure that we set the next transaction
-                    // for the frame that's about to get drawn and not on a previous frame.
-                    mBlastBufferQueue.syncNextTransaction(
-                            t -> {
-                                mHandler.postAtFrontOfQueue(() -> {
-                                    mSurfaceChangedTransaction.merge(t);
-                                    if (blastSyncConsumer != null) {
-                                        blastSyncConsumer.accept(mSurfaceChangedTransaction);
-                                    }
-
-                                    if (reportNextDraw) {
-                                        pendingDrawFinished();
-                                    }
-                                });
-                            });
-                }
-
-                return createFrameCommitCallbackForSync(useBlastSync, reportNextDraw,
-                        blastSyncConsumer);
-            }
-        };
+        });
     }
 
-    private void performDraw(boolean useBlastSync) {
+    private void performDraw() {
         if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
             return;
         } else if (mView == null) {
             return;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw || useBlastSync;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || mSyncBufferCallback != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
 
-        FrameDrawingCallback frameDrawingCallback = createFrameDrawingCallbackIfNeeded(useBlastSync,
-                mReportNextDraw);
-        if (frameDrawingCallback != null) {
-            mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frameDrawingCallback);
-        }
+        registerFrameDrawingCallbackForBlur();
         addFrameCommitCallbackIfNeeded();
-        boolean usingAsyncReport = isHardwareEnabled() && (useBlastSync || mReportNextDraw);
+
+        boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null;
+        if (usingAsyncReport) {
+            registerCallbacksForSync(mSyncBuffer, mSyncBufferCallback);
+        } else if (mHasPendingTransactions) {
+            // These callbacks are only needed if there's no sync involved and there were calls to
+            // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
+            // apply those transactions directly so they don't get stuck forever.
+            registerCallbackForPendingTransactions();
+        }
+        mHasPendingTransactions = false;
 
         try {
             boolean canUseAsync = draw(fullRedrawNeeded);
             if (usingAsyncReport && !canUseAsync) {
                 mAttachInfo.mThreadedRenderer.setFrameCallback(null);
                 usingAsyncReport = false;
-                mAttachInfo.mThreadedRenderer.unregisterRtFrameCallback(frameDrawingCallback);
             }
         } finally {
             mIsDrawing = false;
@@ -4400,7 +4293,6 @@
         }
 
         if (mReportNextDraw) {
-            mReportNextDraw = false;
 
             // if we're using multi-thread renderer, wait for the window frame draws
             if (mWindowDrawCountDown != null) {
@@ -4421,7 +4313,11 @@
             }
 
             if (mSurfaceHolder != null && mSurface.isValid()) {
-                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
+                final SurfaceSyncer.SyncBufferCallback syncBufferCallback = mSyncBufferCallback;
+                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
+                        mHandler.post(() -> syncBufferCallback.onBufferReady(null)));
+                mSyncBufferCallback = null;
+
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
 
                 sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
@@ -4429,9 +4325,11 @@
                 if (mAttachInfo.mThreadedRenderer != null) {
                     mAttachInfo.mThreadedRenderer.fence();
                 }
-                pendingDrawFinished();
             }
         }
+        if (mSyncBufferCallback != null && !usingAsyncReport) {
+            mSyncBufferCallback.onBufferReady(null);
+        }
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
         }
@@ -5440,7 +5338,6 @@
     private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
     private static final int MSG_UPDATE_POINTER_ICON = 27;
     private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
-    private static final int MSG_DRAW_FINISHED = 29;
     private static final int MSG_INSETS_CHANGED = 30;
     private static final int MSG_INSETS_CONTROL_CHANGED = 31;
     private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
@@ -5503,8 +5400,6 @@
                     return "MSG_UPDATE_POINTER_ICON";
                 case MSG_POINTER_CAPTURE_CHANGED:
                     return "MSG_POINTER_CAPTURE_CHANGED";
-                case MSG_DRAW_FINISHED:
-                    return "MSG_DRAW_FINISHED";
                 case MSG_INSETS_CHANGED:
                     return "MSG_INSETS_CHANGED";
                 case MSG_INSETS_CONTROL_CHANGED:
@@ -5735,9 +5630,6 @@
                     final boolean hasCapture = msg.arg1 != 0;
                     handlePointerCaptureChanged(hasCapture);
                 } break;
-                case MSG_DRAW_FINISHED: {
-                    pendingDrawFinished();
-                } break;
                 case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
                     systemGestureExclusionChanged();
                 }   break;
@@ -8066,7 +7958,6 @@
 
     private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
             boolean insetsPending) throws RemoteException {
-
         mRelayoutRequested = true;
         float appScale = mAttachInfo.mApplicationScale;
         boolean restore = false;
@@ -8639,7 +8530,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, boolean forceLayout,
-            boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) {
+            boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, int resizeMode) {
+
         Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
         SomeArgs args = SomeArgs.obtain();
         final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
@@ -8649,8 +8541,9 @@
         args.argi1 = forceLayout ? 1 : 0;
         args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
         args.argi3 = displayId;
-        args.argi4 = seqId;
+        args.argi4 = syncSeqId;
         args.argi5 = resizeMode;
+
         msg.obj = args;
         mHandler.sendMessage(msg);
     }
@@ -9942,8 +9835,8 @@
     }
 
     private void reportNextDraw() {
-        if (mReportNextDraw == false) {
-            drawPending();
+        if (DEBUG_BLAST) {
+            Log.d(mTag, "reportNextDraw " + Debug.getCallers(5));
         }
         mReportNextDraw = true;
     }
@@ -9954,9 +9847,14 @@
      * This method is only supposed to be used to speed up the interaction from SystemUI and window
      * manager when waiting for the first frame to be drawn when turning on the screen. DO NOT USE
      * unless you fully understand this interaction.
+     *
+     * @param syncBuffer If true, the transaction that contains the buffer from the draw should be
+     *                   sent to system to be synced. If false, VRI will not try to sync the buffer,
+     *                   but only report back that a buffer was drawn.
      * @hide
      */
-    public void setReportNextDraw() {
+    public void setReportNextDraw(boolean syncBuffer) {
+        mSyncBuffer = syncBuffer;
         reportNextDraw();
         invalidate();
     }
@@ -10035,11 +9933,11 @@
         @Override
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, boolean forceLayout,
-                boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) {
+                boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, int resizeMode) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
                 viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, forceLayout,
-                        alwaysConsumeSystemBars, displayId, seqId, resizeMode);
+                        alwaysConsumeSystemBars, displayId, syncSeqId, resizeMode);
             }
         }
 
@@ -10982,14 +10880,15 @@
         return mWindowSession;
     }
 
-    private void registerCallbacksForSync(
+    private void registerCallbacksForSync(boolean syncBuffer,
             final SurfaceSyncer.SyncBufferCallback syncBufferCallback) {
         if (!isHardwareEnabled()) {
-            // TODO: correctly handle when hardware disabled
-            syncBufferCallback.onBufferReady(null);
             return;
         }
 
+        if (DEBUG_BLAST) {
+            Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer);
+        }
         mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() {
             @Override
             public void onFrameDraw(long frame) {
@@ -11018,7 +10917,9 @@
                     Log.d(mTag, "Setting up sync and frameCommitCallback");
                 }
 
-                mBlastBufferQueue.syncNextTransaction(t -> syncBufferCallback.onBufferReady(t));
+                if (syncBuffer) {
+                    mBlastBufferQueue.syncNextTransaction(syncBufferCallback::onBufferReady);
+                }
 
                 return didProduceBuffer -> {
                     if (DEBUG_BLAST) {
@@ -11032,18 +10933,40 @@
                     // were only set for the current draw attempt.
                     if (!didProduceBuffer) {
                         mBlastBufferQueue.syncNextTransaction(null);
+
                         // Gather the transactions that were sent to mergeWithNextTransaction
                         // since the frame didn't draw on this vsync. It's possible the frame will
                         // draw later, but it's better to not be sync than to block on a frame that
                         // may never come.
                         syncBufferCallback.onBufferReady(
                                 mBlastBufferQueue.gatherPendingTransactions(frame));
+                        return;
+                    }
+
+                    // If we didn't request to sync a buffer, then we won't get the
+                    // syncNextTransaction callback. Instead, just report back to the Syncer so it
+                    // knows that this sync request is complete.
+                    if (!syncBuffer) {
+                        syncBufferCallback.onBufferReady(null);
                     }
                 };
             }
         });
     }
 
-    public final SurfaceSyncer.SyncTarget mSyncTarget =
-            syncBufferCallback -> registerCallbacksForSync(syncBufferCallback);
+    public final SurfaceSyncer.SyncTarget mSyncTarget = this::readyToSync;
+
+    private void readyToSync(SurfaceSyncer.SyncBufferCallback syncBufferCallback) {
+        if (mSyncBufferCallback != null) {
+            Log.d(mTag, "Already set sync for the next draw.");
+            mSyncBufferCallback.onBufferReady(null);
+        }
+        if (DEBUG_BLAST) {
+            Log.d(mTag, "Setting syncFrameCallback");
+        }
+        mSyncBufferCallback = syncBufferCallback;
+        if (!mIsInTraversal && !mTraversalScheduled) {
+            scheduleTraversals();
+        }
+    }
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 07db91f..6fa6d39 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3077,11 +3077,26 @@
     }
 
     /**
-     * Like {@link #showAutofillDialog(View)} but for virtual views.
+     * If autofill suggestions for a
+     * <a href="{@docRoot}reference/android/service/autofill/Dataset.html#FillDialogUI">
+     * dialog-style UI</a> are available for virtual {@code view}, shows a dialog allowing the user
+     * to select a suggestion and returns {@code true}.
+     * <p>
+     * The dialog may not be shown if the autofill service does not support it, if the autofill
+     * request has not returned a response yet, if the dialog was shown previously, or if the
+     * input method is already shown.
+     * <p>
+     * It is recommended apps to call this method the first time a user focuses on
+     * an autofill-able form, and to avoid showing the input method if the dialog is shown. If
+     * this method returns {@code false}, you should then instead show the input method (assuming
+     * that is how the view normally handles the focus event). If the user re-focuses on the view,
+     * you should not call this method again so as to not disrupt usage of the input method.
      *
-     * @param virtualId id identifying the virtual child inside the parent view.
+     * @param view the view hosting the virtual view hierarchy which is used to show autofill
+     *            suggestions.
+     * @param virtualId id identifying the virtual view inside the host view.
+     * @return {@code true} if the autofill dialog is being shown
      */
-    // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
     public boolean showAutofillDialog(@NonNull View view, int virtualId) {
         Objects.requireNonNull(view);
         if (shouldShowAutofillDialog(getAutofillId(view, virtualId))) {
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index 9c9baf3..f1dc5e7 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
@@ -30,10 +31,14 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewRootImpl;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.internal.policy.PhoneWindow;
 
@@ -115,6 +120,27 @@
     private CharSequence mPlayDescription;
     private CharSequence mPauseDescription;
     private final AccessibilityManager mAccessibilityManager;
+    private boolean mBackCallbackRegistered;
+    /** Handles back invocation */
+    private final OnBackInvokedCallback mBackCallback = new OnBackInvokedCallback() {
+        @Override
+        public void onBackInvoked() {
+            hide();
+        }
+    };
+    /** Handles decor view attach state change */
+    private final OnAttachStateChangeListener mAttachStateListener =
+            new OnAttachStateChangeListener() {
+        @Override
+        public void onViewAttachedToWindow(@NonNull View v) {
+            registerOnBackInvokedCallback();
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(@NonNull View v) {
+            unregisterOnBackInvokedCallback();
+        }
+    };
 
     public MediaController(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -151,6 +177,7 @@
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
         mDecor = mWindow.getDecorView();
         mDecor.setOnTouchListener(mTouchListener);
+        mDecor.addOnAttachStateChangeListener(mAttachStateListener);
         mWindow.setContentView(this);
         mWindow.setBackgroundDrawableResource(android.R.color.transparent);
 
@@ -395,6 +422,7 @@
             removeCallbacks(mFadeOut);
             postDelayed(mFadeOut, timeout);
         }
+        registerOnBackInvokedCallback();
     }
 
     public boolean isShowing() {
@@ -416,6 +444,7 @@
                 Log.w("MediaController", "already removed");
             }
             mShowing = false;
+            unregisterOnBackInvokedCallback();
         }
     }
 
@@ -718,6 +747,35 @@
         }
     }
 
+    private void unregisterOnBackInvokedCallback() {
+        if (!mBackCallbackRegistered) {
+            return;
+        }
+        ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
+        if (viewRootImpl != null
+                && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(
+                viewRootImpl.mContext)) {
+            viewRootImpl.getOnBackInvokedDispatcher()
+                    .unregisterOnBackInvokedCallback(mBackCallback);
+        }
+        mBackCallbackRegistered = false;
+    }
+
+    private void registerOnBackInvokedCallback() {
+        if (mBackCallbackRegistered) {
+            return;
+        }
+
+        ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
+        if (viewRootImpl != null
+                && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(
+                viewRootImpl.mContext)) {
+            viewRootImpl.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
+            mBackCallbackRegistered = true;
+        }
+    }
+
     public interface MediaPlayerControl {
         void    start();
         void    pause();
diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
index 31c3822..2d6c77f 100644
--- a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
@@ -56,7 +56,11 @@
 
         CharSequence streamedDeviceName = intent.getCharSequenceExtra(EXTRA_STREAMED_DEVICE);
         if (!TextUtils.isEmpty(streamedDeviceName)) {
-            mAlertParams.mTitle = getString(R.string.app_streaming_blocked_title, appLabel);
+            mAlertParams.mTitle =
+                    TextUtils.equals(activityInfo.packageName,
+                        getPackageManager().getPermissionControllerPackageName())
+                            ? getString(R.string.app_streaming_blocked_title_for_permission_dialog)
+                            : getString(R.string.app_streaming_blocked_title, appLabel);
             mAlertParams.mMessage =
                     getString(R.string.app_streaming_blocked_message, streamedDeviceName);
         } else {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f623a73..4b20347 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -69,6 +69,7 @@
 import android.os.connectivity.WifiActivityEnergyInfo;
 import android.os.connectivity.WifiBatteryStats;
 import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.CellSignalStrength;
 import android.telephony.CellSignalStrengthLte;
@@ -6830,6 +6831,27 @@
         }
     }
 
+    @RadioAccessTechnology
+    private static int mapRadioAccessNetworkTypeToRadioAccessTechnology(
+            @AccessNetworkConstants.RadioAccessNetworkType int dataType) {
+        switch (dataType) {
+            case AccessNetworkConstants.AccessNetworkType.NGRAN:
+                return RADIO_ACCESS_TECHNOLOGY_NR;
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+                return RADIO_ACCESS_TECHNOLOGY_LTE;
+            case AccessNetworkConstants.AccessNetworkType.UNKNOWN: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.GERAN: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.UTRAN: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.CDMA2000: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.IWLAN:
+                return RADIO_ACCESS_TECHNOLOGY_OTHER;
+            default:
+                Slog.w(TAG,
+                        "Unhandled RadioAccessNetworkType (" + dataType + "), mapping to OTHER");
+                return RADIO_ACCESS_TECHNOLOGY_OTHER;
+        }
+    }
+
     @GuardedBy("this")
     public void noteWifiOnLocked() {
         noteWifiOnLocked(mClock.elapsedRealtime(), mClock.uptimeMillis());
@@ -13721,66 +13743,7 @@
                     mTmpRailStats.resetCellularTotalEnergyUsed();
                 }
 
-                // Proportionally smear Rx and Tx times across each RAt
-                final int levelCount = CellSignalStrength.getNumSignalStrengthLevels();
-                long[] perSignalStrengthActiveTimeMs = new long[levelCount];
-                long totalActiveTimeMs = 0;
-
-                for (int rat = 0; rat < RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
-                    final RadioAccessTechnologyBatteryStats ratStats = mPerRatBatteryStats[rat];
-                    if (ratStats == null) continue;
-
-                    final int freqCount = ratStats.getFrequencyRangeCount();
-                    for (int freq = 0; freq < freqCount; freq++) {
-                        for (int level = 0; level < levelCount; level++) {
-                            final long durationMs = ratStats.getTimeSinceMark(freq, level,
-                                    elapsedRealtimeMs);
-                            perSignalStrengthActiveTimeMs[level] += durationMs;
-                            totalActiveTimeMs += durationMs;
-                        }
-                    }
-                }
-
-                if (totalActiveTimeMs != 0) {
-                    // Smear the provided Tx/Rx durations across each RAT, frequency, and signal
-                    // strength.
-                    for (int rat = 0; rat < RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
-                        final RadioAccessTechnologyBatteryStats ratStats = mPerRatBatteryStats[rat];
-                        if (ratStats == null) continue;
-
-                        final int freqCount = ratStats.getFrequencyRangeCount();
-                        for (int freq = 0; freq < freqCount; freq++) {
-                            long frequencyDurationMs = 0;
-                            for (int level = 0; level < levelCount; level++) {
-                                final long durationMs = ratStats.getTimeSinceMark(freq, level,
-                                        elapsedRealtimeMs);
-                                final long totalLvlDurationMs =
-                                        perSignalStrengthActiveTimeMs[level];
-                                if (totalLvlDurationMs == 0) continue;
-                                final long totalTxLvlDurations =
-                                        deltaInfo.getTransmitDurationMillisAtPowerLevel(level);
-                                // Smear HAL provided Tx power level duration based on active modem
-                                // duration in a given state. (Add totalLvlDurationMs / 2 before
-                                // the integer division with totalLvlDurationMs for rounding.)
-                                final long proportionalTxDurationMs =
-                                        (durationMs * totalTxLvlDurations
-                                                + (totalLvlDurationMs / 2)) / totalLvlDurationMs;
-                                ratStats.incrementTxDuration(freq, level, proportionalTxDurationMs);
-                                frequencyDurationMs += durationMs;
-                            }
-                            final long totalRxDuration = deltaInfo.getReceiveTimeMillis();
-                            // Smear HAL provided Rx power duration based on active modem
-                            // duration in a given state.  (Add totalActiveTimeMs / 2 before the
-                            // integer division with totalActiveTimeMs for rounding.)
-                            final long proportionalRxDurationMs =
-                                    (frequencyDurationMs * totalRxDuration + (totalActiveTimeMs
-                                            / 2)) / totalActiveTimeMs;
-                            ratStats.incrementRxDuration(freq, proportionalRxDurationMs);
-                        }
-
-                        ratStats.setMark(elapsedRealtimeMs);
-                    }
-                }
+                incrementPerRatDataLocked(deltaInfo, elapsedRealtimeMs);
             }
             long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
                     elapsedRealtimeMs * 1000);
@@ -13931,6 +13894,100 @@
         }
     }
 
+    @GuardedBy("this")
+    private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
+        final int infoSize = deltaInfo.getSpecificInfoLength();
+        if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
+                == AccessNetworkConstants.AccessNetworkType.UNKNOWN
+                && deltaInfo.getSpecificInfoFrequencyRange(0)
+                == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+            // Specific info data unavailable. Proportionally smear Rx and Tx times across each RAT.
+            final int levelCount = CellSignalStrength.getNumSignalStrengthLevels();
+            long[] perSignalStrengthActiveTimeMs = new long[levelCount];
+            long totalActiveTimeMs = 0;
+
+            for (int rat = 0; rat < RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+                final RadioAccessTechnologyBatteryStats ratStats = mPerRatBatteryStats[rat];
+                if (ratStats == null) continue;
+
+                final int freqCount = ratStats.getFrequencyRangeCount();
+                for (int freq = 0; freq < freqCount; freq++) {
+                    for (int level = 0; level < levelCount; level++) {
+                        final long durationMs = ratStats.getTimeSinceMark(freq, level,
+                                elapsedRealtimeMs);
+                        perSignalStrengthActiveTimeMs[level] += durationMs;
+                        totalActiveTimeMs += durationMs;
+                    }
+                }
+            }
+            if (totalActiveTimeMs != 0) {
+                // Smear the provided Tx/Rx durations across each RAT, frequency, and signal
+                // strength.
+                for (int rat = 0; rat < RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+                    final RadioAccessTechnologyBatteryStats ratStats = mPerRatBatteryStats[rat];
+                    if (ratStats == null) continue;
+
+                    final int freqCount = ratStats.getFrequencyRangeCount();
+                    for (int freq = 0; freq < freqCount; freq++) {
+                        long frequencyDurationMs = 0;
+                        for (int level = 0; level < levelCount; level++) {
+                            final long durationMs = ratStats.getTimeSinceMark(freq, level,
+                                    elapsedRealtimeMs);
+                            final long totalLvlDurationMs =
+                                    perSignalStrengthActiveTimeMs[level];
+                            if (totalLvlDurationMs == 0) continue;
+                            final long totalTxLvlDurations =
+                                    deltaInfo.getTransmitDurationMillisAtPowerLevel(level);
+                            // Smear HAL provided Tx power level duration based on active modem
+                            // duration in a given state. (Add totalLvlDurationMs / 2 before
+                            // the integer division with totalLvlDurationMs for rounding.)
+                            final long proportionalTxDurationMs =
+                                    (durationMs * totalTxLvlDurations
+                                            + (totalLvlDurationMs / 2)) / totalLvlDurationMs;
+                            ratStats.incrementTxDuration(freq, level, proportionalTxDurationMs);
+                            frequencyDurationMs += durationMs;
+                        }
+                        final long totalRxDuration = deltaInfo.getReceiveTimeMillis();
+                        // Smear HAL provided Rx power duration based on active modem
+                        // duration in a given state.  (Add totalActiveTimeMs / 2 before the
+                        // integer division with totalActiveTimeMs for rounding.)
+                        final long proportionalRxDurationMs =
+                                (frequencyDurationMs * totalRxDuration + (totalActiveTimeMs
+                                        / 2)) / totalActiveTimeMs;
+                        ratStats.incrementRxDuration(freq, proportionalRxDurationMs);
+                    }
+
+                }
+            }
+        } else {
+            // Specific data available.
+            for (int index = 0; index < infoSize; index++) {
+                final int rat = deltaInfo.getSpecificInfoRat(index);
+                final int freq = deltaInfo.getSpecificInfoFrequencyRange(index);
+
+                // Map RadioAccessNetworkType to course grain RadioAccessTechnology.
+                final int ratBucket = mapRadioAccessNetworkTypeToRadioAccessTechnology(rat);
+                final RadioAccessTechnologyBatteryStats ratStats = getRatBatteryStatsLocked(
+                        ratBucket);
+
+                final long rxTimeMs = deltaInfo.getReceiveTimeMillis(rat, freq);
+                final int[] txTimesMs = deltaInfo.getTransmitTimeMillis(rat, freq);
+
+                ratStats.incrementRxDuration(freq, rxTimeMs);
+                final int numTxLvl = txTimesMs.length;
+                for (int lvl = 0; lvl < numTxLvl; lvl++) {
+                    ratStats.incrementTxDuration(freq, lvl, txTimesMs[lvl]);
+                }
+            }
+        }
+
+        for (int rat = 0; rat < RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+            final RadioAccessTechnologyBatteryStats ratStats = mPerRatBatteryStats[rat];
+            if (ratStats == null) continue;
+            ratStats.setMark(elapsedRealtimeMs);
+        }
+    }
+
     /**
      * Add modem tx power to history
      * Device is said to be in high cellular transmit power when it has spent most of the transmit
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c371ccd..d30980f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5464,6 +5464,8 @@
 
     <!-- Title of the dialog shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_title"><xliff:g id="activity" example="Permission dialog">%1$s</xliff:g> unavailable</string>
+    <!-- Title of the dialog shown when the permissioncontroller is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_title_for_permission_dialog">Permission needed</string>
     <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
     <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2bb3e65..f1a8e56 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3300,6 +3300,7 @@
   <java-symbol type="string" name="app_blocked_message" />
 
   <java-symbol type="string" name="app_streaming_blocked_title" />
+  <java-symbol type="string" name="app_streaming_blocked_title_for_permission_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_message" />
 
   <!-- Used internally for assistant to launch activity transitions -->
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index dbe1e81..87c45dc 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -32,12 +32,15 @@
 
 import android.app.ActivityManager;
 import android.app.usage.NetworkStatsManager;
+import android.hardware.radio.V1_5.AccessNetwork;
 import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ActivityStatsTechSpecificInfo;
 import android.telephony.Annotation;
 import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
@@ -58,8 +61,10 @@
 
 import org.mockito.Mock;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.IntConsumer;
 
@@ -1322,7 +1327,7 @@
             }
         }
 
-        final ModemAndBatteryState state = new ModemAndBatteryState(bi, null);
+        final ModemAndBatteryState state = new ModemAndBatteryState(bi, null, null);
 
         IntConsumer incrementTime = inc -> {
             state.currentTimeMs += inc;
@@ -1469,32 +1474,16 @@
         final long[][][] expectedTxDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
         for (int rat = 0; rat < ratCount; rat++) {
             for (int freq = 0; freq < frequencyCount; freq++) {
-                if (rat != RADIO_ACCESS_TECHNOLOGY_NR
-                        && freq != ServiceState.FREQUENCY_RANGE_UNKNOWN) {
-                    // Only the NR RAT should have per frequency data.
-                    expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE;
-                } else {
-                    expectedRxDurationsMs[rat][freq] = 0;
-                }
                 expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE;
 
                 for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
-                    expectedDurationsMs[rat][freq][txLvl] = 0;
-
-                    if (rat != RADIO_ACCESS_TECHNOLOGY_NR
-                            && freq != ServiceState.FREQUENCY_RANGE_UNKNOWN) {
-                        // Only the NR RAT should have per frequency data.
-                        expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE;
-                    } else {
-                        expectedTxDurationsMs[rat][freq][txLvl] = 0;
-                    }
                     expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE;
                 }
             }
         }
 
         final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L);
-        final ModemAndBatteryState state = new ModemAndBatteryState(bi, mai);
+        final ModemAndBatteryState state = new ModemAndBatteryState(bi, mai, null);
 
         IntConsumer incrementTime = inc -> {
             state.currentTimeMs += inc;
@@ -1711,6 +1700,288 @@
                 expectedTxDurationsMs, bi, state.currentTimeMs);
     }
 
+    @SmallTest
+    public void testGetPerStateActiveRadioDurationMs_withSpecificInfoModemActivity() {
+        final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+        bi.setPowerProfile(mock(PowerProfile.class));
+        final int ratCount = RADIO_ACCESS_TECHNOLOGY_COUNT;
+        final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+        List<ActivityStatsTechSpecificInfo> specificInfoList = new ArrayList();
+
+        final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        final long[][] expectedRxDurationsMs = new long[ratCount][frequencyCount];
+        final long[][][] expectedTxDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        for (int rat = 0; rat < ratCount; rat++) {
+            for (int freq = 0; freq < frequencyCount; freq++) {
+                if (rat == RADIO_ACCESS_TECHNOLOGY_NR
+                        || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                    // Initialize available specific Modem info
+                    specificInfoList.add(
+                            new ActivityStatsTechSpecificInfo(rat, freq, new int[txLevelCount], 0));
+                }
+                expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE;
+
+                for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+                    expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE;
+                }
+            }
+        }
+
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UNKNOWN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.GERAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UTRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.EUTRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.CDMA2000,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.IWLAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+                ServiceState.FREQUENCY_RANGE_LOW, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+                ServiceState.FREQUENCY_RANGE_MID, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+                ServiceState.FREQUENCY_RANGE_HIGH, new int[txLevelCount], 0));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+                ServiceState.FREQUENCY_RANGE_MMWAVE, new int[txLevelCount], 0));
+
+        final ActivityStatsTechSpecificInfo[] specificInfos = specificInfoList.toArray(
+                new ActivityStatsTechSpecificInfo[specificInfoList.size()]);
+        final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, specificInfos);
+        final ModemAndBatteryState state = new ModemAndBatteryState(bi, mai, specificInfos);
+
+        IntConsumer incrementTime = inc -> {
+            state.currentTimeMs += inc;
+            clock.realtime = clock.uptime = state.currentTimeMs;
+
+            // If the device is not on battery, no timers should increment.
+            if (!state.onBattery) return;
+            // If the modem is not active, no timers should increment.
+            if (!state.modemActive) return;
+
+            final int currRat = state.currentRat;
+            final int currRant = state.currentRadioAccessNetworkType;
+            final int currFreqRange =
+                    currRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+            int currSignalStrength = state.currentSignalStrengths.get(currRat);
+
+            expectedDurationsMs[currRat][currFreqRange][currSignalStrength] += inc;
+
+            // Evaluate the HAL provided time in states.
+            final ActivityStatsTechSpecificInfo info = state.getSpecificInfo(currRant,
+                    currFreqRange);
+            switch (state.modemState) {
+                case SLEEP:
+                    long sleepMs = state.modemActivityInfo.getSleepTimeMillis();
+                    state.modemActivityInfo.setSleepTimeMillis(sleepMs + inc);
+                    break;
+                case IDLE:
+                    long idleMs = state.modemActivityInfo.getIdleTimeMillis();
+                    state.modemActivityInfo.setIdleTimeMillis(idleMs + inc);
+                    break;
+                case RECEIVING:
+                    long rxMs = info.getReceiveTimeMillis();
+                    info.setReceiveTimeMillis(rxMs + inc);
+                    expectedRxDurationsMs[currRat][currFreqRange] += inc;
+                    break;
+                case TRANSMITTING:
+                    int[] txMs = info.getTransmitTimeMillis().clone();
+                    txMs[currSignalStrength] += inc;
+                    info.setTransmitTimeMillis(txMs);
+                    expectedTxDurationsMs[currRat][currFreqRange][currSignalStrength] += inc;
+                    break;
+            }
+        };
+
+        state.setOnBattery(false);
+        state.setModemActive(false);
+        state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                AccessNetworkConstants.AccessNetworkType.UNKNOWN);
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // While not on battery, the timers should not increase.
+        state.setModemActive(true);
+        incrementTime.accept(100);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+                AccessNetworkConstants.AccessNetworkType.NGRAN);
+        incrementTime.accept(200);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
+        incrementTime.accept(300);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN);
+        incrementTime.accept(400);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Data will now be available.
+        for (int rat = 0; rat < ratCount; rat++) {
+            for (int freq = 0; freq < frequencyCount; freq++) {
+                if (rat == RADIO_ACCESS_TECHNOLOGY_NR
+                        || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                    // Only the NR RAT should have per frequency data.
+                    expectedRxDurationsMs[rat][freq] = 0;
+                }
+                for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+                    if (rat == RADIO_ACCESS_TECHNOLOGY_NR
+                            || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                        // Only the NR RAT should have per frequency data.
+                        expectedTxDurationsMs[rat][freq][txLvl] = 0;
+                    }
+                }
+            }
+        }
+
+        // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
+        // start counting up.
+        state.setOnBattery(true);
+        state.noteModemControllerActivity();
+        incrementTime.accept(300);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(500);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(600);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+        // Changing LTE signal strength should be tracked.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(300);
+        state.setModemState(ModemState.SLEEP);
+        incrementTime.accept(1000);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(700);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        incrementTime.accept(800);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(222);
+        state.setModemState(ModemState.IDLE);
+        incrementTime.accept(111);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(7777);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(88);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(900);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+        incrementTime.accept(123);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(333);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(1000);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(555);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Change in the signal strength of nonactive RAT should not affect anything.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(631);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(321);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(99);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Changing to OTHER Rat should start tracking the poor signal strength.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                AccessNetworkConstants.AccessNetworkType.CDMA2000);
+        incrementTime.accept(1200);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Noting frequency change should not affect non NR Rat.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
+        incrementTime.accept(444);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(1300);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+                AccessNetworkConstants.AccessNetworkType.NGRAN);
+        incrementTime.accept(1400);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Frequency changed to low.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+        incrementTime.accept(852);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(157);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(1500);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Modem no longer active, should not be tracking any more.
+        state.setModemActive(false);
+        incrementTime.accept(1500);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
@@ -1841,17 +2112,22 @@
         public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         @BatteryStats.RadioAccessTechnology
         public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+        @AccessNetworkConstants.RadioAccessNetworkType
+        public int currentRadioAccessNetworkType = AccessNetworkConstants.AccessNetworkType.UNKNOWN;
         @ServiceState.FrequencyRange
         public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
         public SparseIntArray currentSignalStrengths = new SparseIntArray();
         public ModemState modemState = ModemState.SLEEP;
         public ModemActivityInfo modemActivityInfo;
+        public ActivityStatsTechSpecificInfo[] specificInfo;
 
         private final MockBatteryStatsImpl mBsi;
 
-        ModemAndBatteryState(MockBatteryStatsImpl bsi, ModemActivityInfo mai) {
+        ModemAndBatteryState(MockBatteryStatsImpl bsi, ModemActivityInfo mai,
+                ActivityStatsTechSpecificInfo[] astsi) {
             mBsi = bsi;
             modemActivityInfo = mai;
+            specificInfo = astsi;
         }
 
         void setOnBattery(boolean onBattery) {
@@ -1871,6 +2147,13 @@
         }
 
         void setRatType(@Annotation.NetworkType int dataType,
+                @BatteryStats.RadioAccessTechnology int rat,
+                @AccessNetworkConstants.RadioAccessNetworkType int halDataType) {
+            currentRadioAccessNetworkType = halDataType;
+            setRatType(dataType, rat);
+        }
+
+        void setRatType(@Annotation.NetworkType int dataType,
                 @BatteryStats.RadioAccessTechnology int rat) {
             currentNetworkDataType = dataType;
             currentRat = rat;
@@ -1895,13 +2178,45 @@
             modemState = state;
         }
 
+        ActivityStatsTechSpecificInfo getSpecificInfo(@BatteryStats.RadioAccessTechnology int rat,
+                @ServiceState.FrequencyRange int frequency) {
+            if (specificInfo == null) return null;
+            for (ActivityStatsTechSpecificInfo info : specificInfo) {
+                if (info.getRat() == rat && info.getFrequencyRange() == frequency) {
+                    return info;
+                }
+            }
+            return null;
+        }
+
         void noteModemControllerActivity() {
             if (modemActivityInfo == null) return;
             modemActivityInfo.setTimestamp(currentTimeMs);
-            ModemActivityInfo copy = new ModemActivityInfo(modemActivityInfo.getTimestampMillis(),
-                    modemActivityInfo.getSleepTimeMillis(), modemActivityInfo.getIdleTimeMillis(),
-                    modemActivityInfo.getTransmitTimeMillis().clone(),
-                    modemActivityInfo.getReceiveTimeMillis());
+            final ModemActivityInfo copy;
+            if (specificInfo == null) {
+                copy = new ModemActivityInfo(
+                        modemActivityInfo.getTimestampMillis(),
+                        modemActivityInfo.getSleepTimeMillis(),
+                        modemActivityInfo.getIdleTimeMillis(),
+                        modemActivityInfo.getTransmitTimeMillis().clone(),
+                        modemActivityInfo.getReceiveTimeMillis());
+            } else {
+                // Deep copy specificInfo
+                final ActivityStatsTechSpecificInfo[] infoCopies =
+                        new ActivityStatsTechSpecificInfo[specificInfo.length];
+                for (int i = 0; i < specificInfo.length; i++) {
+                    final ActivityStatsTechSpecificInfo info = specificInfo[i];
+                    infoCopies[i] = new ActivityStatsTechSpecificInfo(info.getRat(),
+                            info.getFrequencyRange(), info.getTransmitTimeMillis().clone(),
+                            (int) info.getReceiveTimeMillis());
+                }
+
+                copy = new ModemActivityInfo(
+                        modemActivityInfo.getTimestampMillis(),
+                        modemActivityInfo.getSleepTimeMillis(),
+                        modemActivityInfo.getIdleTimeMillis(),
+                        infoCopies);
+            }
             mBsi.noteModemControllerActivity(copy, POWER_DATA_UNAVAILABLE,
                     currentTimeMs, currentTimeMs, mNetworkStatsManager);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index da8308e..10ff2fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -692,6 +692,7 @@
      * @param bubblePosition the x position of the bubble if showing on top, the y position of
      *                       the bubble if showing vertically.
      * @param onLeft whether the stack was on the left side of the screen when expanded.
+     * @param animate whether the pointer should animate to this position.
      */
     public void setPointerPosition(float bubblePosition, boolean onLeft, boolean animate) {
         // Pointer gets drawn in the padding
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 8a120b9..97e5ee3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -579,11 +579,10 @@
 
         // Showing vertically: might need to translate the bubbles above the IME.
         // Subtract spacing here to provide a margin between top of IME and bottom of bubble row.
-        final float bottomInset = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2);
+        final float bottomHeight = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2);
+        final float bottomInset = mScreenRect.bottom - bottomHeight;
         final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
-        final float centerPosition = showBubblesVertically()
-                ? mPositionRect.centerY()
-                : mPositionRect.centerX();
+        final float centerPosition = mPositionRect.centerY();
         final float rowBottom = centerPosition + (expandedStackSize / 2f);
         final float rowTop = centerPosition - (expandedStackSize / 2f);
         float rowTopForIme = rowTop;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 33115e9..ad5d85c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -96,7 +96,7 @@
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
 public class PipController implements PipTransitionController.PipTransitionCallback,
-        RemoteCallable<PipController>, DisplayController.OnDisplaysChangedListener {
+        RemoteCallable<PipController> {
     private static final String TAG = "PipController";
 
     private Context mContext;
@@ -234,6 +234,14 @@
                     onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
                             true /* saveRestoreSnapFraction */);
                 }
+
+                @Override
+                public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
+                        Set<Rect> unrestricted) {
+                    if (mPipBoundsState.getDisplayId() == displayId) {
+                        mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+                    }
+                }
             };
 
     /**
@@ -463,14 +471,6 @@
         return mMainExecutor;
     }
 
-    @Override
-    public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
-            Set<Rect> unrestricted) {
-        if (mPipBoundsState.getDisplayId() == displayId) {
-            mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
-        }
-    }
-
     private void onConfigurationChanged(Configuration newConfig) {
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
         mTouchHandler.onConfigurationChanged();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index af6e5d4..aef298e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -217,7 +217,8 @@
         final Rect keepClearArea = new Rect(0, 0, 10, 10);
         when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
 
-        mPipController.onKeepClearAreasChanged(displayId, Set.of(keepClearArea), Set.of());
+        mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
+                displayId, Set.of(keepClearArea), Set.of());
 
         verify(mMockPipBoundsState).setKeepClearAreas(Set.of(keepClearArea), Set.of());
     }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 395062e..491a5cd 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -8402,6 +8402,12 @@
      * {@link #addAssistantServicesUids(int[])} and not yet removed with
      * {@link #removeAssistantServicesUids(int[])}
      *
+     * <p> Note that during native audioserver crash and after boot up the list of assistant
+     * UIDs will be reset to an empty list (i.e. no UID will be considered as assistant)
+     * Just after user switch, the list of assistant will also reset to empty.
+     * In both cases,The component's UID of the assistiant role or assistant setting will be
+     * automitically added to the list by the audio service.
+     *
      * @return array of assistants UIDs
      *
      * @hide
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
index 858f85f..6928b94 100644
--- a/media/packages/BluetoothMidiService/AndroidManifest.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -20,8 +20,6 @@
         xmlns:tools="http://schemas.android.com/tools"
         package="com.android.bluetoothmidiservice"
         >
-    <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
-
     <uses-feature android:name="android.hardware.bluetooth_le"
          android:required="true"/>
     <uses-feature android:name="android.software.midi"
diff --git a/media/packages/BluetoothMidiService/AndroidManifestBase.xml b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
index 99dcaaa..468f6ee 100644
--- a/media/packages/BluetoothMidiService/AndroidManifestBase.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
@@ -19,7 +19,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.bluetoothmidiservice"
           >
-    <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
     <application
         android:label="BluetoothMidi"
         android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 955aee9..03384a2 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -110,7 +110,7 @@
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
     <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
     <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
-    <uses-permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
+    <uses-permission android:name="android.permission.TRIGGER_LOST_MODE" />
     <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <!-- ACCESS_MTP is needed for testing purposes only. -->
diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md
index e75ae29..0a35725 100644
--- a/packages/SystemUI/docs/broadcasts.md
+++ b/packages/SystemUI/docs/broadcasts.md
@@ -21,6 +21,7 @@
 * The `IntentFilter` may or may not contain categories.
 * The `IntentFilter` **does not** contain data types, data schemes, data authorities or data paths.
 * The broadcast **is not** gated behind a permission.
+* The broadcast **is not** ordered and doesn't need to set result data.
 
 Additionally, the dispatcher supports the following:
 
@@ -107,3 +108,8 @@
 ```
 
 Unregistering can be done even if the `BroadcastReceiver` has never been registered with `BroadcastDispatcher`. In that case, it is a No-Op.
+
+### A note on goAsync()
+
+If you're processing a broadcast in a background thread, you shouldn't call `goAsync()` and
+`finish()`. The system will keep sysui alive regardless, so it isn't needed.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 8ad2009..75d95e6 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -123,6 +123,11 @@
     default void setScrollListener(ScrollListener scrollListener) {}
 
     /**
+     * Sets the amount of vertical over scroll that should be performed on QS.
+     */
+    default void setOverScrollAmount(int overScrollAmount) {}
+
+    /**
      * Callback for when QSPanel container is scrolled
      */
     @ProvidesInterface(version = ScrollListener.VERSION)
diff --git a/packages/SystemUI/res/drawable/ic_media_pause.xml b/packages/SystemUI/res/drawable/ic_media_pause.xml
index 1f4b2cf..0009b6c 100644
--- a/packages/SystemUI/res/drawable/ic_media_pause.xml
+++ b/packages/SystemUI/res/drawable/ic_media_pause.xml
@@ -1,26 +1,91 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="14dp"
-    android:height="16dp"
-    android:viewportWidth="14"
-    android:viewportHeight="16">
-    <path
-        android:pathData="M9.1818,15.6363H13.5455V0.3635H9.1818V15.6363ZM0.4546,15.6363H4.8182V0.3635H0.4546V15.6363Z"
-        android:fillColor="#FFFFFF"
-        android:fillType="evenOdd"/>
-</vector>
\ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?>

+<!--

+  ~ Copyright (C) 2022 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

+-->

+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"

+                 xmlns:aapt="http://schemas.android.com/aapt">

+    <aapt:attr name="android:drawable">

+        <vector android:height="24dp"

+                android:width="24dp"

+                android:viewportHeight="24"

+                android:viewportWidth="24">

+            <group android:name="_R_G">

+                <group android:name="_R_G_L_1_G"

+                       android:translateX="12"

+                       android:translateY="12.125">

+                    <path android:name="_R_G_L_1_G_D_0_P_0"

+                          android:fillColor="#ffffff"

+                          android:fillAlpha="1"

+                          android:fillType="nonZero"

+                          android:pathData=" M-6 7 C-6,7 -2,7 -2,7 C-2,7 -2,-7 -2,-7 C-2,-7 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "/>

+                </group>

+                <group android:name="_R_G_L_0_G"

+                       android:translateX="12"

+                       android:translateY="12.125">

+                    <path android:name="_R_G_L_0_G_D_0_P_0"

+                          android:fillColor="#ffffff"

+                          android:fillAlpha="1"

+                          android:fillType="nonZero"

+                          android:pathData=" M2 7 C2,7 6,7 6,7 C6,7 6,-0.12 6,-0.12 C6,-0.12 6,-7 6,-7 C6,-7 2,-7 2,-7 C2,-7 2,7 2,7c "/>

+                </group>

+            </group>

+            <group android:name="time_group"/>

+        </vector>

+    </aapt:attr>

+    <target android:name="_R_G_L_1_G_D_0_P_0">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="pathData"

+                                android:duration="333"

+                                android:startOffset="0"

+                                android:valueFrom="M-6 7 C-6,7 -2,7 -2,7 C-2,7 -2,-7 -2,-7 C-2,-7 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "

+                                android:valueTo="M-6 7 C-6,7 -2,3.94 -2,3.94 C-2,3.94 -2,-4.37 -2,-4.37 C-2,-4.37 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "

+                                android:valueType="pathType">

+                    <aapt:attr name="android:interpolator">

+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>

+                    </aapt:attr>

+                </objectAnimator>

+            </set>

+        </aapt:attr>

+    </target>

+    <target android:name="_R_G_L_0_G_D_0_P_0">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="pathData"

+                                android:duration="333"

+                                android:startOffset="0"

+                                android:valueFrom="M2 7 C2,7 6,7 6,7 C6,7 6,-0.12 6,-0.12 C6,-0.12 6,-7 6,-7 C6,-7 2,-7 2,-7 C2,-7 2,7 2,7c "

+                                android:valueTo="M-5.62 7 C-5.62,7 -4.75,7 -4.75,7 C-4.75,7 6,-0.12 6,-0.12 C6,-0.12 -4.44,-7 -4.44,-7 C-4.44,-7 -5.62,-7 -5.62,-7 C-5.62,-7 -5.62,7 -5.62,7c "

+                                android:valueType="pathType">

+                    <aapt:attr name="android:interpolator">

+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>

+                    </aapt:attr>

+                </objectAnimator>

+            </set>

+        </aapt:attr>

+    </target>

+    <target android:name="time_group">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="translateX"

+                                android:duration="517"

+                                android:startOffset="0"

+                                android:valueFrom="0"

+                                android:valueTo="1"

+                                android:valueType="floatType"/>

+            </set>

+        </aapt:attr>

+    </target>

+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_pause_container.xml b/packages/SystemUI/res/drawable/ic_media_pause_container.xml
new file mode 100644
index 0000000..b92e635
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_pause_container.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>

+<!--

+  ~ Copyright (C) 2022 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

+-->

+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"

+                 xmlns:aapt="http://schemas.android.com/aapt">

+    <aapt:attr name="android:drawable">

+        <vector android:height="48dp"

+                android:width="48dp"

+                android:viewportHeight="48"

+                android:viewportWidth="48">

+            <group android:name="_R_G">

+                <group android:name="_R_G_L_1_G"

+                       android:translateX="24"

+                       android:translateY="24"

+                       android:scaleX="0.5"

+                       android:scaleY="0.5"/>

+                <group android:name="_R_G_L_0_G"

+                       android:translateX="24"

+                       android:translateY="24"

+                       android:scaleX="0.5"

+                       android:scaleY="0.5">

+                    <path android:name="_R_G_L_0_G_D_0_P_0"

+                          android:fillColor="#ffddb3"

+                          android:fillAlpha="1"

+                          android:fillType="nonZero"

+                          android:pathData=" M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "/>

+                </group>

+            </group>

+            <group android:name="time_group"/>

+        </vector>

+    </aapt:attr>

+    <target android:name="_R_G_L_0_G_D_0_P_0">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="pathData"

+                                android:duration="500"

+                                android:startOffset="0"

+                                android:valueFrom="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "

+                                android:valueTo="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "

+                                android:valueType="pathType">

+                    <aapt:attr name="android:interpolator">

+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.526,0 0,1 1.0,1.0"/>

+                    </aapt:attr>

+                </objectAnimator>

+            </set>

+        </aapt:attr>

+    </target>

+    <target android:name="time_group">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="translateX"

+                                android:duration="517"

+                                android:startOffset="0"

+                                android:valueFrom="0"

+                                android:valueTo="1"

+                                android:valueType="floatType"/>

+            </set>

+        </aapt:attr>

+    </target>

+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_play.xml b/packages/SystemUI/res/drawable/ic_media_play.xml
index 0eac1ad..eb32470 100644
--- a/packages/SystemUI/res/drawable/ic_media_play.xml
+++ b/packages/SystemUI/res/drawable/ic_media_play.xml
@@ -1,26 +1,91 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:pathData="M20,12L6,21V3L20,12ZM15.26,12L8.55,7.68V16.32L15.26,12Z"
-        android:fillColor="#FFFFFF"
-        android:fillType="evenOdd"/>
-</vector>
\ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?>

+<!--

+  ~ Copyright (C) 2022 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

+-->

+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"

+                 xmlns:aapt="http://schemas.android.com/aapt">

+    <aapt:attr name="android:drawable">

+        <vector android:height="24dp"

+                android:width="24dp"

+                android:viewportHeight="24"

+                android:viewportWidth="24">

+            <group android:name="_R_G">

+                <group android:name="_R_G_L_1_G"

+                       android:translateX="12"

+                       android:translateY="12.125">

+                    <path android:name="_R_G_L_1_G_D_0_P_0"

+                          android:fillColor="#ffffff"

+                          android:fillAlpha="1"

+                          android:fillType="nonZero"

+                          android:pathData=" M-6 7 C-6,7 -2,3.94 -2,3.94 C-2,3.94 -2,-4.37 -2,-4.37 C-2,-4.37 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "/>

+                </group>

+                <group android:name="_R_G_L_0_G"

+                       android:translateX="12"

+                       android:translateY="12.125">

+                    <path android:name="_R_G_L_0_G_D_0_P_0"

+                          android:fillColor="#ffffff"

+                          android:fillAlpha="1"

+                          android:fillType="nonZero"

+                          android:pathData=" M-5.62 7 C-5.62,7 -4.75,7 -4.75,7 C-4.75,7 6,-0.12 6,-0.12 C6,-0.12 -4.44,-7 -4.44,-7 C-4.44,-7 -5.62,-7 -5.62,-7 C-5.62,-7 -5.62,7 -5.62,7c "/>

+                </group>

+            </group>

+            <group android:name="time_group"/>

+        </vector>

+    </aapt:attr>

+    <target android:name="_R_G_L_1_G_D_0_P_0">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="pathData"

+                                android:duration="333"

+                                android:startOffset="0"

+                                android:valueFrom="M-6 7 C-6,7 -2,3.94 -2,3.94 C-2,3.94 -2,-4.37 -2,-4.37 C-2,-4.37 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "

+                                android:valueTo="M-6 7 C-6,7 -2,7 -2,7 C-2,7 -2,-7 -2,-7 C-2,-7 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "

+                                android:valueType="pathType">

+                    <aapt:attr name="android:interpolator">

+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>

+                    </aapt:attr>

+                </objectAnimator>

+            </set>

+        </aapt:attr>

+    </target>

+    <target android:name="_R_G_L_0_G_D_0_P_0">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="pathData"

+                                android:duration="333"

+                                android:startOffset="0"

+                                android:valueFrom="M-5.62 7 C-5.62,7 -4.75,7 -4.75,7 C-4.75,7 6,-0.12 6,-0.12 C6,-0.12 -4.44,-7 -4.44,-7 C-4.44,-7 -5.62,-7 -5.62,-7 C-5.62,-7 -5.62,7 -5.62,7c "

+                                android:valueTo="M2 7 C2,7 6,7 6,7 C6,7 6,-0.12 6,-0.12 C6,-0.12 6,-7 6,-7 C6,-7 2,-7 2,-7 C2,-7 2,7 2,7c "

+                                android:valueType="pathType">

+                    <aapt:attr name="android:interpolator">

+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>

+                    </aapt:attr>

+                </objectAnimator>

+            </set>

+        </aapt:attr>

+    </target>

+    <target android:name="time_group">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="translateX"

+                                android:duration="517"

+                                android:startOffset="0"

+                                android:valueFrom="0"

+                                android:valueTo="1"

+                                android:valueType="floatType"/>

+            </set>

+        </aapt:attr>

+    </target>

+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_play_container.xml b/packages/SystemUI/res/drawable/ic_media_play_container.xml
new file mode 100644
index 0000000..2fc9fc8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_play_container.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>

+<!--

+  ~ Copyright (C) 2022 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

+-->

+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"

+                 xmlns:aapt="http://schemas.android.com/aapt">

+    <aapt:attr name="android:drawable">

+        <vector android:height="48dp"

+                android:width="48dp"

+                android:viewportHeight="48"

+                android:viewportWidth="48">

+            <group android:name="_R_G">

+                <group android:name="_R_G_L_0_G"

+                       android:translateX="24"

+                       android:translateY="24"

+                       android:scaleX="0.5"

+                       android:scaleY="0.5">

+                    <path android:name="_R_G_L_0_G_D_0_P_0"

+                          android:fillColor="#ffddb3"

+                          android:fillAlpha="1"

+                          android:fillType="nonZero"

+                          android:pathData=" M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "/>

+                </group>

+            </group>

+            <group android:name="time_group"/>

+        </vector>

+    </aapt:attr>

+    <target android:name="_R_G_L_0_G_D_0_P_0">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="pathData"

+                                android:duration="500"

+                                android:startOffset="0"

+                                android:valueFrom="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "

+                                android:valueTo="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "

+                                android:valueType="pathType">

+                    <aapt:attr name="android:interpolator">

+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.518,0 0,1 1.0,1.0"/>

+                    </aapt:attr>

+                </objectAnimator>

+            </set>

+        </aapt:attr>

+    </target>

+    <target android:name="time_group">

+        <aapt:attr name="android:animation">

+            <set android:ordering="together">

+                <objectAnimator android:propertyName="translateX"

+                                android:duration="517"

+                                android:startOffset="0"

+                                android:valueFrom="0"

+                                android:valueTo="1"

+                                android:valueType="floatType"/>

+            </set>

+        </aapt:attr>

+    </target>

+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 1117356..e1f3eca 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -145,8 +145,7 @@
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-         />
+        android:layout_marginEnd="@dimen/qs_media_padding" />
 
     <!-- See comment in media_session_collapsed.xml for how these barriers are used -->
     <androidx.constraintlayout.widget.Barrier
@@ -184,7 +183,7 @@
     <!-- Button visibility will be controlled in code -->
     <ImageButton
         android:id="@+id/actionPrev"
-        style="@style/MediaPlayer.SessionAction"
+        style="@style/MediaPlayer.SessionAction.Secondary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="4dp"
@@ -211,7 +210,7 @@
 
     <ImageButton
         android:id="@+id/actionNext"
-        style="@style/MediaPlayer.SessionAction"
+        style="@style/MediaPlayer.SessionAction.Secondary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="0dp"
@@ -221,7 +220,7 @@
 
     <ImageButton
         android:id="@+id/action0"
-        style="@style/MediaPlayer.SessionAction"
+        style="@style/MediaPlayer.SessionAction.Secondary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -231,7 +230,7 @@
 
     <ImageButton
         android:id="@+id/action1"
-        style="@style/MediaPlayer.SessionAction"
+        style="@style/MediaPlayer.SessionAction.Secondary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -241,7 +240,7 @@
 
     <ImageButton
         android:id="@+id/action2"
-        style="@style/MediaPlayer.SessionAction"
+        style="@style/MediaPlayer.SessionAction.Secondary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -251,7 +250,7 @@
 
     <ImageButton
         android:id="@+id/action3"
-        style="@style/MediaPlayer.SessionAction"
+        style="@style/MediaPlayer.SessionAction.Secondary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -261,7 +260,7 @@
 
     <ImageButton
         android:id="@+id/action4"
-        style="@style/MediaPlayer.SessionAction"
+        style="@style/MediaPlayer.SessionAction.Secondary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginStart="@dimen/qs_media_action_spacing"
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 9f14d2f2..76780ff 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -38,6 +38,8 @@
 
     <dimen name="status_bar_header_height_keyguard">42dp</dimen>
 
+    <dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
+
     <!-- Distance that the full shade transition takes in order to complete by tapping on a button
          like "expand". -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-land/integers.xml b/packages/SystemUI/res/values-sw600dp-land/integers.xml
new file mode 100644
index 0000000..919d605
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-land/integers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2006, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+<resources>
+    <integer name="lockscreen_shade_over_scroll_release_duration">500</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index cff660d..0512d3c 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -26,4 +26,6 @@
     <dimen name="status_bar_header_height_keyguard">56dp</dimen>
 
     <dimen name="qs_media_session_height_expanded">251dp</dimen>
+
+    <dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a309cc4..f39b5ef 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1174,9 +1174,8 @@
          the shade -->
     <dimen name="lockscreen_shade_media_transition_distance">120dp</dimen>
 
-    <!-- Maximum overshoot for the topPadding of notifications when transitioning to the full
-         shade -->
-    <dimen name="lockscreen_shade_notification_movement">24dp</dimen>
+    <!-- Maximum over scroll amount for the shade when transition to the full shade. -->
+    <dimen name="lockscreen_shade_max_over_scroll_amount">24dp</dimen>
 
     <!-- Maximum overshoot for the pulse expansion -->
     <dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index f0f7a19..3164ed1 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -25,4 +25,7 @@
          See com.android.systemui.volume.VolumeDialogImpl.
          Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
     <integer name="volume_dialog_gravity">21</integer>
+
+    <!-- The time it takes for the over scroll release animation to complete, in milli seconds.  -->
+    <integer name="lockscreen_shade_over_scroll_release_duration">0</integer>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a61eda8..4776587 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -616,7 +616,6 @@
            parent="@android:style/Widget.Material.Button.Borderless.Small">
         <item name="android:background">@drawable/qs_media_light_source</item>
         <item name="android:tint">?android:attr/textColorPrimary</item>
-        <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
         <item name="android:paddingTop">12dp</item>
         <item name="android:paddingStart">12dp</item>
         <item name="android:paddingEnd">12dp</item>
@@ -629,6 +628,10 @@
         <item name="android:backgroundTint">@color/media_player_solid_button_bg</item>
     </style>
 
+    <style name="MediaPlayer.SessionAction.Secondary" parent="MediaPlayer.SessionAction">
+        <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
+    </style>
+
     <style name="MediaPlayer.OutlineButton">
         <item name="android:background">@drawable/qs_media_outline_button</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57997d8..1a325d3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -233,6 +233,13 @@
                     mSecurityViewFlipperController.reloadColors();
                 }
             };
+    private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+            new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onDevicePolicyManagerStateChanged() {
+            showPrimarySecurityScreen(false);
+        }
+    };
 
     private KeyguardSecurityContainerController(KeyguardSecurityContainer view,
             AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
@@ -279,6 +286,7 @@
 
     @Override
     protected void onViewAttached() {
+        mUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mView.setSwipeListener(mSwipeListener);
         mView.addMotionEventListener(mGlobalTouchListener);
         mConfigurationController.addCallback(mConfigurationListener);
@@ -286,6 +294,7 @@
 
     @Override
     protected void onViewDetached() {
+        mUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
         mConfigurationController.removeCallback(mConfigurationListener);
         mView.removeMotionEventListener(mGlobalTouchListener);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5602f3d..fcabbbc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2723,12 +2723,20 @@
     }
 
     /**
-     * Handle {@link #MSG_DPM_STATE_CHANGED}
+     * Handle {@link #MSG_DPM_STATE_CHANGED} which can change primary authentication methods to
+     * pin/pattern/password/none.
      */
     private void handleDevicePolicyManagerStateChanged(int userId) {
         Assert.isMainThread();
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         updateSecondaryLockscreenRequirement(userId);
+
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onDevicePolicyManagerStateChanged();
+            }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index ad2053c..9373ea8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -300,6 +300,11 @@
     public void onSecondaryLockscreenRequirementChanged(int userId) { }
 
     /**
+     * Called when device policy manager state changes.
+     */
+    public void onDevicePolicyManagerStateChanged() { }
+
+    /**
      * Called when notifying user to unlock in order to use NFC.
      */
     public void onRequireUnlockForNfc() { }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index 4c00735..085bcfa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -33,6 +33,8 @@
 import android.hardware.fingerprint.ISidefpsController
 import android.os.Handler
 import android.util.Log
+import android.view.View.AccessibilityDelegate
+import android.view.accessibility.AccessibilityEvent
 import android.view.Display
 import android.view.Gravity
 import android.view.LayoutInflater
@@ -181,6 +183,23 @@
         }
         lottie.addOverlayDynamicColor(context)
 
+        /**
+         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator
+         * is in focus
+         */
+        view.setAccessibilityDelegate(object : AccessibilityDelegate() {
+            override fun dispatchPopulateAccessibilityEvent(
+                host: View,
+                event: AccessibilityEvent
+            ): Boolean {
+                return if (event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+                    true
+                } else {
+                    super.dispatchPopulateAccessibilityEvent(host, event)
+                }
+            }
+        })
         return view
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index bbb75c3..0ff5805 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -60,6 +60,8 @@
  * Use only for IntentFilters with actions and optionally categories. It does not support,
  * permissions, schemes, data types, data authorities or priority different than 0.
  * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
+ * Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
+ * and doesn't need to worry about being killed.
  */
 open class BroadcastDispatcher constructor (
     private val context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index aa43383..4065a25 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -540,13 +540,15 @@
     /**
      * Removes a complication by {@link ComplicationId}.
      */
-    public void removeComplication(ComplicationId id) {
-        if (!mEntries.containsKey(id)) {
+    public boolean removeComplication(ComplicationId id) {
+        final ViewEntry entry = mEntries.remove(id);
+
+        if (entry == null) {
             Log.e(TAG, "could not find id:" + id);
-            return;
+            return false;
         }
 
-        final ViewEntry entry = mEntries.get(id);
         entry.remove();
+        return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d36bb72..758609a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2373,7 +2373,8 @@
                 // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
                 // the next draw from here, so we don't have to wait for window manager to signal
                 // this to our ViewRootImpl.
-                mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
+                mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw(
+                        false /* syncBuffer */);
                 mScreenOnCoordinator.setWakeAndUnlocking(false);
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index d2f953f..b8da46e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -30,13 +30,14 @@
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
 import android.os.Process;
-import android.text.Layout;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -67,6 +68,7 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -637,9 +639,34 @@
 
     private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
             ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) {
+        AnimationBindHandler animHandler;
+        if (button.getTag() == null) {
+            animHandler = new AnimationBindHandler();
+            button.setTag(animHandler);
+        } else {
+            animHandler = (AnimationBindHandler) button.getTag();
+        }
+
+        animHandler.tryExecute(() -> {
+            bindSemanticButton(animHandler, button, mediaAction,
+                               collapsedSet, expandedSet, showInCompact);
+        });
+    }
+
+    private void bindSemanticButton(final AnimationBindHandler animHandler,
+            final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet,
+            ConstraintSet expandedSet, boolean showInCompact) {
+
+        animHandler.unregisterAll();
         if (mediaAction != null) {
-            button.setImageIcon(mediaAction.getIcon());
+            final Drawable icon = mediaAction.getIcon();
+            button.setImageDrawable(icon);
             button.setContentDescription(mediaAction.getContentDescription());
+            final Drawable bgDrawable = mediaAction.getBackground();
+            button.setBackground(bgDrawable);
+
+            animHandler.tryRegister(icon);
+            animHandler.tryRegister(bgDrawable);
 
             Runnable action = mediaAction.getAction();
             if (action == null) {
@@ -651,19 +678,75 @@
                         logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
                                 /* isRecommendationCard */ false);
                         action.run();
+
+                        if (icon instanceof Animatable) {
+                            ((Animatable) icon).start();
+                        }
+                        if (bgDrawable instanceof Animatable) {
+                            ((Animatable) bgDrawable).start();
+                        }
                     }
                 });
             }
         } else {
-            button.setImageIcon(null);
+            button.setImageDrawable(null);
             button.setContentDescription(null);
             button.setEnabled(false);
+            button.setBackground(mContext.getDrawable(R.drawable.qs_media_round_button_background));
         }
 
         setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact);
         setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
     }
 
+    private static class AnimationBindHandler extends Animatable2.AnimationCallback {
+        private ArrayList<Runnable> mOnAnimationsComplete = new ArrayList<>();
+        private ArrayList<Animatable2> mRegistrations = new ArrayList<>();
+
+        public void tryRegister(Drawable drawable) {
+            if (drawable instanceof Animatable2) {
+                Animatable2 anim = (Animatable2) drawable;
+                anim.registerAnimationCallback(this);
+                mRegistrations.add(anim);
+            }
+        }
+
+        public void unregisterAll() {
+            for (Animatable2 anim : mRegistrations) {
+                anim.unregisterAnimationCallback(this);
+            }
+            mRegistrations.clear();
+        }
+
+        public boolean isAnimationRunning() {
+            for (Animatable2 anim : mRegistrations) {
+                if (anim.isRunning()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void tryExecute(Runnable action) {
+            if (isAnimationRunning()) {
+                mOnAnimationsComplete.add(action);
+            } else {
+                action.run();
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Drawable drawable) {
+            super.onAnimationEnd(drawable);
+            if (!isAnimationRunning()) {
+                for (Runnable action : mOnAnimationsComplete) {
+                    action.run();
+                }
+                mOnAnimationsComplete.clear();
+            }
+        }
+    }
+
     @Nullable
     private ActivityLaunchAnimator.Controller buildLaunchAnimatorController(
             TransitionLayout player) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index f1712db..47a0991 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -170,9 +170,10 @@
 
 /** State of a media action. */
 data class MediaAction(
-    val icon: Icon?,
+    val icon: Drawable?,
     val action: Runnable?,
-    val contentDescription: CharSequence?
+    val contentDescription: CharSequence?,
+    val background: Drawable?
 )
 
 /** State of the media device. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index f457ae7..5c36cab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -695,11 +695,12 @@
                     Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
                 } else {
                     action.getIcon()
-                }.setTint(themeText)
+                }.setTint(themeText).loadDrawable(context)
                 val mediaAction = MediaAction(
                     mediaActionIcon,
                     runnable,
-                    action.title)
+                    action.title,
+                    context.getDrawable(R.drawable.qs_media_round_button_background))
                 actionIcons.add(mediaAction)
             }
         }
@@ -789,30 +790,34 @@
         return when (action) {
             PlaybackState.ACTION_PLAY -> {
                 MediaAction(
-                    Icon.createWithResource(context, R.drawable.ic_media_play),
+                    context.getDrawable(R.drawable.ic_media_play),
                     { controller.transportControls.play() },
-                    context.getString(R.string.controls_media_button_play)
+                    context.getString(R.string.controls_media_button_play),
+                    context.getDrawable(R.drawable.ic_media_play_container)
                 )
             }
             PlaybackState.ACTION_PAUSE -> {
                 MediaAction(
-                    Icon.createWithResource(context, R.drawable.ic_media_pause),
+                    context.getDrawable(R.drawable.ic_media_pause),
                     { controller.transportControls.pause() },
-                    context.getString(R.string.controls_media_button_pause)
+                    context.getString(R.string.controls_media_button_pause),
+                    context.getDrawable(R.drawable.ic_media_pause_container)
                 )
             }
             PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
                 MediaAction(
-                    Icon.createWithResource(context, R.drawable.ic_media_prev),
+                    context.getDrawable(R.drawable.ic_media_prev),
                     { controller.transportControls.skipToPrevious() },
-                    context.getString(R.string.controls_media_button_prev)
+                    context.getString(R.string.controls_media_button_prev),
+                    context.getDrawable(R.drawable.qs_media_round_button_background)
                 )
             }
             PlaybackState.ACTION_SKIP_TO_NEXT -> {
                 MediaAction(
-                    Icon.createWithResource(context, R.drawable.ic_media_next),
+                    context.getDrawable(R.drawable.ic_media_next),
                     { controller.transportControls.skipToNext() },
-                    context.getString(R.string.controls_media_button_next)
+                    context.getString(R.string.controls_media_button_next),
+                    context.getDrawable(R.drawable.qs_media_round_button_background)
                 )
             }
             else -> null
@@ -835,9 +840,10 @@
 
         val it = state.customActions[index]
         return MediaAction(
-            Icon.createWithResource(packageName, it.icon),
+            Icon.createWithResource(packageName, it.icon).loadDrawable(context),
             { controller.transportControls.sendCustomAction(it, it.extras) },
-            it.name
+            it.name,
+            context.getDrawable(R.drawable.ic_media_pause_container)
         )
     }
 
@@ -900,9 +906,11 @@
 
     private fun getResumeMediaAction(action: Runnable): MediaAction {
         return MediaAction(
-            Icon.createWithResource(context, R.drawable.ic_media_play).setTint(themeText),
+            Icon.createWithResource(context, R.drawable.ic_media_play)
+                .setTint(themeText).loadDrawable(context),
             action,
-            context.getString(R.string.controls_media_resume)
+            context.getString(R.string.controls_media_resume),
+            context.getDrawable(R.drawable.ic_media_play_container)
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 4e63104..e751d54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -92,6 +92,7 @@
     private float mLastPanelFraction;
     private float mSquishinessFraction = 1;
     private boolean mQsDisabled;
+    private int[] mTemp = new int[2];
 
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MediaHost mQsMediaHost;
@@ -141,6 +142,8 @@
      */
     private float mFullShadeProgress;
 
+    private boolean mOverScrolling;
+
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             QSTileHost qsTileHost,
@@ -498,6 +501,12 @@
     }
 
     @Override
+    public void setOverScrollAmount(int overScrollAmount) {
+        mOverScrolling = overScrollAmount != 0;
+        getView().setTranslationY(overScrollAmount);
+    }
+
+    @Override
     public int getHeightDiff() {
         return mQSPanelScrollView.getBottom() - mHeader.getBottom()
                 + mHeader.getPaddingBottom();
@@ -515,7 +524,7 @@
                 ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
         boolean onKeyguard = isKeyguardState();
         boolean onKeyguardAndExpanded = onKeyguard && !mShowCollapsedOnKeyguard;
-        if (!mHeaderAnimating && !headerWillBeAnimating()) {
+        if (!mHeaderAnimating && !headerWillBeAnimating() && !mOverScrolling) {
             getView().setTranslationY(
                     onKeyguardAndExpanded
                             ? translationScaleY * mHeader.getHeight()
@@ -600,8 +609,11 @@
         }
         mQSPanelScrollView.setClipBounds(mQsBounds);
 
-        mQsMediaHost.getCurrentClipping().set(0, 0, getView().getMeasuredWidth(),
-                mQSPanelScrollView.getMeasuredHeight() - mQSPanelScrollView.getPaddingBottom());
+        mQSPanelScrollView.getLocationOnScreen(mTemp);
+        int top = mTemp[1];
+        mQsMediaHost.getCurrentClipping().set(0, top, getView().getMeasuredWidth(),
+                top + mQSPanelScrollView.getMeasuredHeight()
+                        - mQSPanelScrollView.getPaddingBottom());
     }
 
     private void updateMediaPositions() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt
new file mode 100644
index 0000000..de37a38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt
@@ -0,0 +1,8 @@
+package com.android.systemui.statusbar
+
+/** Represents an over scroller for the transition to full shade on lock screen. */
+interface LockScreenShadeOverScroller {
+
+    /** The amount in pixels that the user has dragged to expand the shade. */
+    var expansionDragDownAmount: Float
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 310eb4f..8f78feb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -64,6 +64,8 @@
     private val scrimController: ScrimController,
     private val depthController: NotificationShadeDepthController,
     private val context: Context,
+    private val splitShadeOverScrollerFactory: SplitShadeLockScreenOverScroller.Factory,
+    private val singleShadeOverScrollerFactory: SingleShadeLockScreenOverScroller.Factory,
     wakefulnessLifecycle: WakefulnessLifecycle,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager,
@@ -194,6 +196,27 @@
      */
     val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, context)
 
+    private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy {
+        splitShadeOverScrollerFactory.create(qS, nsslController)
+    }
+
+    private val phoneShadeOverScroller: SingleShadeLockScreenOverScroller by lazy {
+        singleShadeOverScrollerFactory.create(nsslController)
+    }
+
+    /**
+     * [LockScreenShadeOverScroller] property that delegates to either
+     * [SingleShadeLockScreenOverScroller] or [SplitShadeLockScreenOverScroller].
+     *
+     * There are currently two different implementations, as the over scroll behavior is different
+     * on single shade and split shade.
+     *
+     * On single shade, only notifications are over scrolled, whereas on split shade, everything is
+     * over scrolled.
+     */
+    private val shadeOverScroller: LockScreenShadeOverScroller
+        get() = if (useSplitShade) splitShadeOverScroller else phoneShadeOverScroller
+
     init {
         updateResources()
         configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
@@ -410,7 +433,7 @@
                 if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
                     val notificationShelfProgress =
                         MathUtils.saturate(dragDownAmount / notificationShelfTransitionDistance)
-                    nsslController.setTransitionToFullShadeAmount(field, notificationShelfProgress)
+                    nsslController.setTransitionToFullShadeAmount(notificationShelfProgress)
 
                     qSDragProgress = MathUtils.saturate(dragDownAmount / qsTransitionDistance)
                     qS.setTransitionToFullShadeAmount(field, qSDragProgress)
@@ -422,6 +445,7 @@
                     transitionToShadeAmountScrim(field)
                     transitionToShadeAmountCommon(field)
                     transitionToShadeAmountKeyguard(field)
+                    shadeOverScroller.expansionDragDownAmount = dragDownAmount
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
new file mode 100644
index 0000000..575f354
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
@@ -0,0 +1,74 @@
+package com.android.systemui.statusbar
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.MathUtils
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class SingleShadeLockScreenOverScroller
+@AssistedInject
+constructor(
+    configurationController: ConfigurationController,
+    private val context: Context,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    @Assisted private val nsslController: NotificationStackScrollLayoutController
+) : LockScreenShadeOverScroller {
+
+    private var maxOverScrollAmount = 0
+    private var totalDistanceForFullShadeTransition = 0
+
+    init {
+        updateResources()
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    updateResources()
+                }
+            })
+    }
+
+    private fun updateResources() {
+        val resources = context.resources
+        totalDistanceForFullShadeTransition =
+            resources.getDimensionPixelSize(R.dimen.lockscreen_shade_qs_transition_distance)
+        maxOverScrollAmount =
+            resources.getDimensionPixelSize(R.dimen.lockscreen_shade_max_over_scroll_amount)
+    }
+
+    override var expansionDragDownAmount: Float = 0f
+        set(value) {
+            if (value == field) {
+                return
+            }
+            field = value
+            overScroll()
+        }
+
+    private fun overScroll() {
+        var extraTopInset = 0.0f
+        if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+            val viewHeight = nsslController.height
+            val overallProgress = MathUtils.saturate(expansionDragDownAmount / viewHeight)
+            val transitionProgress =
+                Interpolators.getOvershootInterpolation(
+                    overallProgress,
+                    0.6f,
+                    totalDistanceForFullShadeTransition.toFloat() / viewHeight.toFloat())
+            extraTopInset = transitionProgress * maxOverScrollAmount
+        }
+        nsslController.setOverScrollAmount(extraTopInset.toInt())
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(
+            nsslController: NotificationStackScrollLayoutController
+        ): SingleShadeLockScreenOverScroller
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
new file mode 100644
index 0000000..96ce6b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -0,0 +1,129 @@
+package com.android.systemui.statusbar
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.animation.PathInterpolator
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class SplitShadeLockScreenOverScroller
+@AssistedInject
+constructor(
+    configurationController: ConfigurationController,
+    private val context: Context,
+    private val scrimController: ScrimController,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    @Assisted private val qS: QS,
+    @Assisted private val nsslController: NotificationStackScrollLayoutController
+) : LockScreenShadeOverScroller {
+
+    private var releaseOverScrollAnimator: Animator? = null
+    private var transitionToFullShadeDistance = 0
+    private var releaseOverScrollDuration = 0L
+    private var maxOverScrollAmount = 0
+    private var previousOverscrollAmount = 0
+
+    init {
+        updateResources()
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    updateResources()
+                }
+            })
+    }
+
+    private fun updateResources() {
+        val resources = context.resources
+        transitionToFullShadeDistance =
+            resources.getDimensionPixelSize(R.dimen.lockscreen_shade_full_transition_distance)
+        maxOverScrollAmount =
+            resources.getDimensionPixelSize(R.dimen.lockscreen_shade_max_over_scroll_amount)
+        releaseOverScrollDuration =
+            resources.getInteger(R.integer.lockscreen_shade_over_scroll_release_duration).toLong()
+    }
+
+    override var expansionDragDownAmount: Float = 0f
+        set(dragDownAmount) {
+            if (field == dragDownAmount) {
+                return
+            }
+            field = dragDownAmount
+            if (shouldOverscroll()) {
+                overScroll(dragDownAmount)
+            } else if (shouldReleaseOverscroll()) {
+                releaseOverScroll()
+            }
+        }
+
+    private fun shouldOverscroll() = statusBarStateController.state == StatusBarState.KEYGUARD
+
+    private fun shouldReleaseOverscroll() = !shouldOverscroll() && previousOverscrollAmount != 0
+
+    private fun overScroll(dragDownAmount: Float) {
+        val overscrollAmount: Int = calculateOverscrollAmount(dragDownAmount)
+        applyOverscroll(overscrollAmount)
+        previousOverscrollAmount = overscrollAmount
+    }
+
+    private fun applyOverscroll(overscrollAmount: Int) {
+        qS.setOverScrollAmount(overscrollAmount)
+        scrimController.setNotificationsOverScrollAmount(overscrollAmount)
+        nsslController.setOverScrollAmount(overscrollAmount)
+    }
+
+    private fun calculateOverscrollAmount(dragDownAmount: Float): Int {
+        val fullHeight: Int = nsslController.height
+        val fullHeightProgress: Float = MathUtils.saturate(dragDownAmount / fullHeight)
+        val overshootStart: Float = transitionToFullShadeDistance / fullHeight.toFloat()
+        val overShootTransitionProgress: Float =
+            Interpolators.getOvershootInterpolation(
+                fullHeightProgress, OVER_SHOOT_AMOUNT, overshootStart)
+        return (overShootTransitionProgress * maxOverScrollAmount).toInt()
+    }
+
+    private fun releaseOverScroll() {
+        val animator = ValueAnimator.ofInt(previousOverscrollAmount, 0)
+        animator.addUpdateListener {
+            val overScrollAmount = it.animatedValue as Int
+            qS.setOverScrollAmount(overScrollAmount)
+            scrimController.setNotificationsOverScrollAmount(overScrollAmount)
+            nsslController.setOverScrollAmount(overScrollAmount)
+        }
+        animator.interpolator = RELEASE_OVER_SCROLL_INTERPOLATOR
+        animator.duration = releaseOverScrollDuration
+        animator.start()
+        releaseOverScrollAnimator = animator
+        previousOverscrollAmount = 0
+    }
+
+    @VisibleForTesting
+    internal fun finishAnimations() {
+        releaseOverScrollAnimator?.end()
+        releaseOverScrollAnimator = null
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(
+            qS: QS,
+            nsslController: NotificationStackScrollLayoutController
+        ): SplitShadeLockScreenOverScroller
+    }
+
+    companion object {
+        private const val OVER_SHOOT_AMOUNT = 0.6f
+        private val RELEASE_OVER_SCROLL_INTERPOLATOR = PathInterpolator(0.17f, 0f, 0f, 1f)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d98f8a7..5bc50ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -41,7 +41,6 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
-import android.util.MathUtils;
 import android.util.Pair;
 import android.view.Display;
 import android.view.LayoutInflater;
@@ -65,7 +64,6 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -205,18 +203,6 @@
 
     private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
 
-    /**
-     * The total distance in pixels that the full shade transition takes to transition entirely to
-     * the full shade.
-     */
-    private int mTotalDistanceForFullShadeTransition;
-
-    /**
-     * The amount of movement the notifications do when transitioning to the full shade before
-     * reaching the overstrech
-     */
-    private int mNotificationDragDownMovement;
-
     @VisibleForTesting
     final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
             new View.OnAttachStateChangeListener() {
@@ -304,10 +290,6 @@
     private NotifStats mNotifStats = NotifStats.getEmpty();
 
     private void updateResources() {
-        mNotificationDragDownMovement = mResources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_notification_movement);
-        mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_qs_transition_distance);
         mNotificationStackSizeCalculator.updateResources();
     }
 
@@ -1537,8 +1519,6 @@
     }
 
     /**
-     * @param amount The amount of pixels we have currently dragged down
-     *               for the lockscreen to shade transition. 0f for all other states.
      * @param fraction The fraction of lockscreen to shade transition.
      *                 0f for all other states.
      *
@@ -1546,18 +1526,15 @@
      * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
      * until the next lockscreen-to-shade transition.
      */
-    public void setTransitionToFullShadeAmount(float amount, float fraction) {
+    public void setTransitionToFullShadeAmount(float fraction) {
         mView.setFractionToShade(fraction);
+    }
 
-        float extraTopInset = 0.0f;
-        if (mStatusBarStateController.getState() == KEYGUARD) {
-            float overallProgress = MathUtils.saturate(amount / mView.getHeight());
-            float transitionProgress = Interpolators.getOvershootInterpolation(overallProgress,
-                    0.6f,
-                    (float) mTotalDistanceForFullShadeTransition / (float) mView.getHeight());
-            extraTopInset = transitionProgress * mNotificationDragDownMovement;
-        }
-        mView.setExtraTopInsetForFullShadeTransition(extraTopInset);
+    /**
+     * Sets the amount of vertical over scroll that should be performed on NSSL.
+     */
+    public void setOverScrollAmount(int overScrollAmount) {
+        mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
     }
 
     /** */
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 7882fff..999ebc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -3210,12 +3210,14 @@
     @Override
     protected void onUnlockHintFinished() {
         super.onUnlockHintFinished();
+        mScrimController.setExpansionAffectsAlpha(true);
         mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
     }
 
     @Override
     protected void onUnlockHintStarted() {
         super.onUnlockHintStarted();
+        mScrimController.setExpansionAffectsAlpha(false);
         mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 7a69dad..7e22510 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -645,6 +645,13 @@
     }
 
     /**
+     * Sets the amount of vertical over scroll that should be performed on the notifications scrim.
+     */
+    public void setNotificationsOverScrollAmount(int overScrollAmount) {
+        mNotificationsScrim.setTranslationY(overScrollAmount);
+    }
+
+    /**
      * Current state of the QuickSettings when pulling it from the top.
      *
      * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index cfac9651..42c3c7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -82,7 +82,6 @@
 import com.android.systemui.util.concurrency.FakeExecution;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -100,7 +99,6 @@
 
 import javax.inject.Provider;
 
-@Ignore
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index d1d9ec3..35bcfcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -18,6 +18,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -414,4 +415,37 @@
             assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
         });
     }
+
+    /**
+     * Ensures a second removal of a complication is a no-op.
+     */
+    @Test
+    public void testDoubleRemoval() {
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        engine.addComplication(firstViewInfo.id, firstViewInfo.view, firstViewInfo.lp,
+                firstViewInfo.category);
+        verify(mLayout).addView(firstViewInfo.view);
+
+        assertThat(engine.removeComplication(firstViewInfo.id)).isTrue();
+        verify(firstViewInfo.view).getParent();
+        verify(mLayout).removeView(firstViewInfo.view);
+
+        Mockito.clearInvocations(mLayout, firstViewInfo.view);
+        assertThat(engine.removeComplication(firstViewInfo.id)).isFalse();
+        verify(firstViewInfo.view, never()).getParent();
+        verify(mLayout, never()).removeView(firstViewInfo.view);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index d0f2816..04609ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -19,8 +19,9 @@
 import org.mockito.Mockito.`when` as whenever
 import android.content.Intent
 import android.graphics.Color
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.Icon
 import android.graphics.drawable.RippleDrawable
 import android.media.MediaMetadata
 import android.media.session.MediaSession
@@ -60,6 +61,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
+import org.mockito.Mockito.any
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -290,15 +292,15 @@
 
     @Test
     fun bindSemanticActions() {
-        val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+        val icon = context.getDrawable(android.R.drawable.ic_media_play)
+        val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
         val semanticActions = MediaButton(
-                playOrPause = MediaAction(icon, Runnable {}, "play"),
-                nextOrCustom = MediaAction(icon, Runnable {}, "next"),
-                custom0 = MediaAction(icon, null, "custom 0"),
-                custom1 = MediaAction(icon, null, "custom 1")
+            playOrPause = MediaAction(icon, Runnable {}, "play", bg),
+            nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
+            custom0 = MediaAction(icon, null, "custom 0", bg),
+            custom1 = MediaAction(icon, null, "custom 1", bg)
         )
         val state = mediaData.copy(semanticActions = semanticActions)
-
         player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
 
@@ -338,10 +340,10 @@
     fun bind_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
         whenever(seekBarViewModel.getEnabled()).thenReturn(false)
 
-        val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+        val icon = context.getDrawable(android.R.drawable.ic_media_play)
         val semanticActions = MediaButton(
-            playOrPause = MediaAction(icon, Runnable {}, "play"),
-            nextOrCustom = MediaAction(icon, Runnable {}, "next")
+            playOrPause = MediaAction(icon, Runnable {}, "play", null),
+            nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
         )
         val state = mediaData.copy(semanticActions = semanticActions)
 
@@ -365,13 +367,14 @@
 
     @Test
     fun bindNotificationActions() {
-        val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+        val icon = context.getDrawable(android.R.drawable.ic_media_play)
+        val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
         val actions = listOf(
-            MediaAction(icon, Runnable {}, "previous"),
-            MediaAction(icon, Runnable {}, "play"),
-            MediaAction(icon, null, "next"),
-            MediaAction(icon, null, "custom 0"),
-            MediaAction(icon, Runnable {}, "custom 1")
+            MediaAction(icon, Runnable {}, "previous", bg),
+            MediaAction(icon, Runnable {}, "play", bg),
+            MediaAction(icon, null, "next", bg),
+            MediaAction(icon, null, "custom 0", bg),
+            MediaAction(icon, Runnable {}, "custom 1", bg)
         )
         val state = mediaData.copy(actions = actions,
             actionsToShowInCompact = listOf(1, 2),
@@ -413,6 +416,72 @@
     }
 
     @Test
+    fun bindAnimatedSemanticActions() {
+        val mockAvd0 = mock(AnimatedVectorDrawable::class.java)
+        val mockAvd1 = mock(AnimatedVectorDrawable::class.java)
+        val mockAvd2 = mock(AnimatedVectorDrawable::class.java)
+        whenever(mockAvd0.mutate()).thenReturn(mockAvd0)
+        whenever(mockAvd1.mutate()).thenReturn(mockAvd1)
+        whenever(mockAvd2.mutate()).thenReturn(mockAvd2)
+
+        val icon = context.getDrawable(R.drawable.ic_media_play)
+        val bg = context.getDrawable(R.drawable.ic_media_play_container)
+        val semanticActions0 = MediaButton(
+                playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null))
+        val semanticActions1 = MediaButton(
+                playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null))
+        val semanticActions2 = MediaButton(
+                playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null))
+        val state0 = mediaData.copy(semanticActions = semanticActions0)
+        val state1 = mediaData.copy(semanticActions = semanticActions1)
+        val state2 = mediaData.copy(semanticActions = semanticActions2)
+
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(state0, PACKAGE)
+
+        // Validate first binding
+        assertThat(actionPlayPause.isEnabled()).isTrue()
+        assertThat(actionPlayPause.contentDescription).isEqualTo("play")
+        verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.VISIBLE)
+        assertThat(actionPlayPause.hasOnClickListeners()).isTrue()
+
+        // Trigger animation & update mock
+        actionPlayPause.performClick()
+        verify(mockAvd0, times(1)).start()
+        whenever(mockAvd0.isRunning()).thenReturn(true)
+
+        // Validate states no longer bind
+        player.bindPlayer(state1, PACKAGE)
+        player.bindPlayer(state2, PACKAGE)
+        assertThat(actionPlayPause.contentDescription).isEqualTo("play")
+
+        // Complete animation and run callbacks
+        whenever(mockAvd0.isRunning()).thenReturn(false)
+        val captor = ArgumentCaptor.forClass(Animatable2.AnimationCallback::class.java)
+        verify(mockAvd0, times(1)).registerAnimationCallback(captor.capture())
+        verify(mockAvd1, never())
+            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd2, never())
+            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        captor.getValue().onAnimationEnd(mockAvd0)
+
+        // Validate correct state was bound
+        assertThat(actionPlayPause.contentDescription).isEqualTo("loading")
+        verify(mockAvd0, times(1))
+            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd1, times(1)
+            ).registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd2, times(1))
+            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd0, times(1))
+            .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd1, times(1))
+            .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd2, never())
+            .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+    }
+
+    @Test
     fun bindText() {
         player.attachPlayer(viewHolder)
         player.bindPlayer(mediaData, PACKAGE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 1d2a0ca..52c8a55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -44,6 +44,7 @@
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.junit.MockitoJUnit
 
 private fun <T> anyObject(): T {
@@ -75,6 +76,8 @@
     @Mock lateinit var expandHelperCallback: ExpandHelper.Callback
     @Mock lateinit var mCentralSurfaces: CentralSurfaces
     @Mock lateinit var qS: QS
+    @Mock lateinit var singleShadeOverScroller: SingleShadeLockScreenOverScroller
+    @Mock lateinit var splitShadeOverScroller: SplitShadeLockScreenOverScroller
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
     private val configurationController = FakeConfigurationController()
@@ -104,7 +107,9 @@
             context = context,
             configurationController = configurationController,
             falsingManager = falsingManager,
-            dumpManager = dumpManager
+            dumpManager = dumpManager,
+            splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
+            singleShadeOverScrollerFactory = { singleShadeOverScroller }
         )
         whenever(nsslController.view).thenReturn(stackscroller)
         whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback)
@@ -229,7 +234,7 @@
     fun testDragDownAmountDoesntCallOutInLockedDownShade() {
         whenever(nsslController.isInLockedDownShade).thenReturn(true)
         transitionController.dragDownAmount = 10f
-        verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
+        verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
         verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
         verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
         verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
@@ -240,7 +245,7 @@
     @Test
     fun testDragDownAmountCallsOut() {
         transitionController.dragDownAmount = 10f
-        verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
+        verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
         verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
         verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
         verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
@@ -388,6 +393,26 @@
         verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
     }
 
+    @Test
+    fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() {
+        disableSplitShade()
+
+        transitionController.dragDownAmount = 10f
+
+        verify(singleShadeOverScroller).expansionDragDownAmount = 10f
+        verifyZeroInteractions(splitShadeOverScroller)
+    }
+
+    @Test
+    fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() {
+        enableSplitShade()
+
+        transitionController.dragDownAmount = 10f
+
+        verify(splitShadeOverScroller).expansionDragDownAmount = 10f
+        verifyZeroInteractions(singleShadeOverScroller)
+    }
+
     private fun enableSplitShade() {
         setSplitShadeEnabled(true)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
new file mode 100644
index 0000000..2606be5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
@@ -0,0 +1,56 @@
+package com.android.systemui.statusbar
+
+import org.mockito.Mockito.`when` as whenever
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.intThat
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SingleShadeLockScreenOverScrollerTest : SysuiTestCase() {
+
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var nsslController: NotificationStackScrollLayoutController
+
+    private lateinit var overScroller: SingleShadeLockScreenOverScroller
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(nsslController.height).thenReturn(1800)
+        overScroller =
+            SingleShadeLockScreenOverScroller(
+                FakeConfigurationController(),
+                context,
+                statusBarStateController,
+                nsslController
+            )
+    }
+
+    @Test
+    fun setDragDownAmount_onKeyguard_overScrolls() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        overScroller.expansionDragDownAmount = 10f
+
+        verify(nsslController).setOverScrollAmount(intThat { it > 0 })
+    }
+
+    @Test
+    fun setDragDownAmount_notOnKeyguard_doesNotOverScroll() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+        overScroller.expansionDragDownAmount = 10f
+
+        verify(nsslController).setOverScrollAmount(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
new file mode 100644
index 0000000..9d5099c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -0,0 +1,124 @@
+package com.android.systemui.statusbar
+
+import org.mockito.Mockito.`when` as whenever
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.intThat
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
+
+    private val configurationController = FakeConfigurationController()
+
+    @Mock private lateinit var scrimController: ScrimController
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var qS: QS
+    @Mock private lateinit var nsslController: NotificationStackScrollLayoutController
+
+    private lateinit var overScroller: SplitShadeLockScreenOverScroller
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(nsslController.height).thenReturn(1800)
+
+        overScroller =
+            SplitShadeLockScreenOverScroller(
+                configurationController,
+                context,
+                scrimController,
+                statusBarStateController,
+                qS,
+                nsslController)
+    }
+
+    @Test
+    fun setDragDownAmount_onKeyguard_appliesOverScroll() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        setDragAmount(1000f)
+
+        verifyOverScrollPerformed()
+    }
+
+    @Test
+    fun setDragDownAmount_notOnKeyguard_doesNotApplyOverScroll() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+        setDragAmount(1000f)
+
+        verifyZeroInteractions(qS)
+        verifyZeroInteractions(scrimController)
+        verifyZeroInteractions(nsslController)
+    }
+
+    @Test
+    fun setDragAmount_onKeyguard_thenNotOnKeyguard_resetsOverScrollToZero() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        setDragAmount(1000f)
+        verifyOverScrollPerformed()
+        reset(qS, scrimController, nsslController)
+
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+        setDragAmount(999f)
+        verifyOverScrollResetToZero()
+    }
+
+    @Test
+    fun setDragAmount_onKeyguard_thenNotOnKeyguard_multipleTimes_resetsOverScrollToZeroOnlyOnce() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        setDragAmount(1000f)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+        setDragAmount(999f)
+        reset(qS, scrimController, nsslController)
+
+        setDragAmount(998f)
+        setDragAmount(997f)
+        setDragAmount(996f)
+        verifyNoMoreOverScrollChanges()
+    }
+
+    private fun verifyOverScrollPerformed() {
+        verify(qS).setOverScrollAmount(intThat { it > 0 })
+        verify(scrimController).setNotificationsOverScrollAmount(intThat { it > 0 })
+        verify(nsslController).setOverScrollAmount(intThat { it > 0 })
+    }
+
+    private fun verifyOverScrollResetToZero() {
+        // Might be more than once as the animator might have multiple values close to zero that
+        // round down to zero.
+        verify(qS, atLeast(1)).setOverScrollAmount(0)
+        verify(scrimController, atLeast(1)).setNotificationsOverScrollAmount(0)
+        verify(nsslController, atLeast(1)).setOverScrollAmount(0)
+    }
+
+    private fun verifyNoMoreOverScrollChanges() {
+        verifyNoMoreInteractions(qS)
+        verifyNoMoreInteractions(scrimController)
+        verifyNoMoreInteractions(nsslController)
+    }
+
+    private fun setDragAmount(dragDownAmount: Float) {
+        overScroller.expansionDragDownAmount = dragDownAmount
+        overScroller.finishAnimations()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 21d03ad..5add2f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -930,6 +930,14 @@
         verify(mLargeScreenShadeHeaderController).setActive(false);
     }
 
+    @Test
+    public void testUnlockAnimationDoesNotAffectScrim() {
+        mNotificationPanelViewController.onUnlockHintStarted();
+        verify(mScrimController).setExpansionAffectsAlpha(false);
+        mNotificationPanelViewController.onUnlockHintFinished();
+        verify(mScrimController).setExpansionAffectsAlpha(true);
+    }
+
     private void triggerPositionClockAndNotifications() {
         mNotificationPanelViewController.closeQs();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 92c0e1a..134ad4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1296,6 +1296,33 @@
         assertThat(mNotificationsScrim.getViewAlpha()).isEqualTo(notifProgress);
     }
 
+    @Test
+    public void setNotificationsOverScrollAmount_setsTranslationYOnNotificationsScrim() {
+        int overScrollAmount = 10;
+
+        mScrimController.setNotificationsOverScrollAmount(overScrollAmount);
+
+        assertThat(mNotificationsScrim.getTranslationY()).isEqualTo(overScrollAmount);
+    }
+
+    @Test
+    public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnBehindScrim() {
+        int overScrollAmount = 10;
+
+        mScrimController.setNotificationsOverScrollAmount(overScrollAmount);
+
+        assertThat(mScrimBehind.getTranslationY()).isEqualTo(0);
+    }
+
+    @Test
+    public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnFrontScrim() {
+        int overScrollAmount = 10;
+
+        mScrimController.setNotificationsOverScrollAmount(overScrollAmount);
+
+        assertThat(mScrimInFront.getTranslationY()).isEqualTo(0);
+    }
+
     private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
         mScrimController.setRawPanelExpansionFraction(expansion);
         finishAnimationsImmediately();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 61e3da8..22c77e9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4290,11 +4290,15 @@
     }
 
     private void onDoubleTapInternal(int displayId) {
+        AccessibilityInputFilter inputFilter = null;
         synchronized (mLock) {
             if (mHasInputFilter && mInputFilter != null) {
-                mInputFilter.onDoubleTap(displayId);
+                inputFilter = mInputFilter;
             }
         }
+        if (inputFilter != null) {
+            inputFilter.onDoubleTap(displayId);
+        }
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 3a26c46..f4c24a8 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -165,7 +165,10 @@
 
     @Override
     public void onTopActivityChanged(ComponentName topActivity, int uid) {
-        if (mActivityListener != null) {
+        // Don't send onTopActivityChanged() callback when topActivity is null because it's defined
+        // as @NonNull in ActivityListener interface. Sends onDisplayEmpty() callback instead when
+        // there is no activity running on virtual display.
+        if (mActivityListener != null && topActivity != null) {
             // Post callback on the main thread so it doesn't block activity launching
             mHandler.post(() ->
                     mActivityListener.onTopActivityChanged(Display.INVALID_DISPLAY, topActivity));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eeff0de..f27ccb76 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17265,8 +17265,8 @@
         }
 
         @Override
-        public void deletePendingTopUid(int uid) {
-            mPendingStartActivityUids.delete(uid);
+        public void deletePendingTopUid(int uid, long nowElapsed) {
+            mPendingStartActivityUids.delete(uid, nowElapsed);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 800f3ad..d635152 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1254,7 +1254,7 @@
                     mService.mServices.foregroundServiceProcStateChangedLocked(uidRec);
                 }
             }
-            mService.mInternal.deletePendingTopUid(uidRec.getUid());
+            mService.mInternal.deletePendingTopUid(uidRec.getUid(), nowElapsed);
         }
         if (mLocalPowerManager != null) {
             mLocalPowerManager.finishUidChanges();
diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java
index 455c75b..bd60057 100644
--- a/services/core/java/com/android/server/am/PendingStartActivityUids.java
+++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java
@@ -55,9 +55,15 @@
         return false;
     }
 
-    synchronized void delete(int uid) {
+    synchronized void delete(int uid, long nowElapsed) {
         final Pair<Integer, Long> pendingPid = mPendingUids.get(uid);
         if (pendingPid != null) {
+            if (nowElapsed < pendingPid.second) {
+                Slog.i(TAG,
+                        "updateOomAdj start time is before than pendingPid added,"
+                        + " don't delete it");
+                return;
+            }
             final long delay = SystemClock.elapsedRealtime() - pendingPid.second;
             if (delay >= 1000 /*ms*/) {
                 Slog.i(TAG,
@@ -87,4 +93,4 @@
     synchronized boolean isPendingTopUid(int uid) {
         return mPendingUids.get(uid) != null;
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 270a61b..1a482e4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -252,8 +252,8 @@
                         return;
                     }
                 }
-                setCommunicationRouteForClient(
-                        cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource);
+                postSetCommunicationRouteForClient(new CommunicationClientInfo(
+                        cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource));
             }
         }
     }
@@ -283,8 +283,8 @@
                         return false;
                     }
                 }
-                setCommunicationRouteForClient(
-                        cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource);
+                postSetCommunicationRouteForClient(new CommunicationClientInfo(
+                        cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource));
             }
         }
         return true;
@@ -348,26 +348,35 @@
     }
 
     /**
-     * Returns the device currently requested for communication use case.
-     * If the current audio mode owner is in the communication route client list,
-     * use this preference.
-     * Otherwise use first client's preference (first client corresponds to latest request).
-     * null is returned if no client is in the list.
-     * @return AudioDeviceAttributes the requested device for communication.
+     * Returns the communication client with the highest priority:
+     * - 1) the client which is currently also controlling the audio mode
+     * - 2) the first client in the stack if there is no audio mode owner
+     * - 3) no client otherwise
+     * @return CommunicationRouteClient the client driving the communication use case routing.
      */
-
     @GuardedBy("mDeviceStateLock")
-    private AudioDeviceAttributes requestedCommunicationDevice() {
-        AudioDeviceAttributes device = null;
-        for (CommunicationRouteClient cl : mCommunicationRouteClients) {
-            if (cl.getPid() == mModeOwnerPid) {
-                device = cl.getDevice();
+    private CommunicationRouteClient topCommunicationRouteClient() {
+        for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+            if (crc.getPid() == mModeOwnerPid) {
+                return crc;
             }
         }
         if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
-            device = mCommunicationRouteClients.get(0).getDevice();
+            return mCommunicationRouteClients.get(0);
         }
+        return null;
+    }
 
+    /**
+     * Returns the device currently requested for communication use case.
+     * Use the device requested by the communication route client selected by
+     * {@link #topCommunicationRouteClient()} if any or none otherwise.
+     * @return AudioDeviceAttributes the requested device for communication.
+     */
+    @GuardedBy("mDeviceStateLock")
+    private AudioDeviceAttributes requestedCommunicationDevice() {
+        CommunicationRouteClient crc = topCommunicationRouteClient();
+        AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "requestedCommunicationDevice, device: "
                     + device + " mode owner pid: " + mModeOwnerPid);
@@ -710,7 +719,7 @@
         }
         synchronized (mDeviceStateLock) {
             mBluetoothScoOn = on;
-            sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
+            postUpdateCommunicationRouteClient(eventSource);
         }
     }
 
@@ -770,7 +779,9 @@
             synchronized (mDeviceStateLock) {
                 AudioDeviceAttributes device =
                         new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
-                setCommunicationRouteForClient(cb, pid, device, scoAudioMode, eventSource);
+
+                postSetCommunicationRouteForClient(new CommunicationClientInfo(
+                        cb, pid, device, scoAudioMode, eventSource));
             }
         }
     }
@@ -788,8 +799,8 @@
                 if (client == null || !client.requestsBluetoothSco()) {
                     return;
                 }
-                setCommunicationRouteForClient(
-                        cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource);
+                postSetCommunicationRouteForClient(new CommunicationClientInfo(
+                        cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource));
             }
         }
     }
@@ -990,6 +1001,61 @@
                 MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset);
     }
 
+    /*package*/ void postUpdateCommunicationRouteClient(String eventSource) {
+        sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
+    }
+
+    /*package*/ void postSetCommunicationRouteForClient(CommunicationClientInfo info) {
+        sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT, SENDMSG_QUEUE, info);
+    }
+
+    /*package*/ void postScoAudioStateChanged(int state) {
+        sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
+    }
+
+    /*package*/ static final class CommunicationClientInfo {
+        final @NonNull IBinder mCb;
+        final int mPid;
+        final @NonNull AudioDeviceAttributes mDevice;
+        final int mScoAudioMode;
+        final @NonNull String mEventSource;
+
+        CommunicationClientInfo(@NonNull IBinder cb, int pid, @NonNull AudioDeviceAttributes device,
+                int scoAudioMode, @NonNull String eventSource) {
+            mCb = cb;
+            mPid = pid;
+            mDevice = device;
+            mScoAudioMode = scoAudioMode;
+            mEventSource = eventSource;
+        }
+
+        // redefine equality op so we can match messages intended for this client
+        @Override
+        public boolean equals(Object o) {
+            if (o == null) {
+                return false;
+            }
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof CommunicationClientInfo)) {
+                return false;
+            }
+
+            return mCb.equals(((CommunicationClientInfo) o).mCb)
+                    && mPid == ((CommunicationClientInfo) o).mPid;
+        }
+
+        @Override
+        public String toString() {
+            return "CommunicationClientInfo mCb=" + mCb.toString()
+                    +"mPid=" + mPid
+                    +"mDevice=" + mDevice.toString()
+                    +"mScoAudioMode=" + mScoAudioMode
+                    +"mEventSource=" + mEventSource;
+        }
+    }
+
     //---------------------------------------------------------------------
     // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
     // only call from a "handle"* method or "on"* method
@@ -1265,18 +1331,30 @@
                         synchronized (mDeviceStateLock) {
                             mModeOwnerPid = msg.arg1;
                             if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
-                                onUpdateCommunicationRoute("setNewModeOwner");
+                                onUpdateCommunicationRouteClient("setNewModeOwner");
                             }
                         }
                     }
                     break;
-                case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
+
+                case MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj);
+                            CommunicationClientInfo info = (CommunicationClientInfo) msg.obj;
+                            setCommunicationRouteForClient(info.mCb, info.mPid, info.mDevice,
+                                    info.mScoAudioMode, info.mEventSource);
                         }
                     }
                     break;
+
+                case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT:
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            onUpdateCommunicationRouteClient((String) msg.obj);
+                        }
+                    }
+                    break;
+
                 case MSG_L_UPDATE_COMMUNICATION_ROUTE:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
@@ -1284,6 +1362,23 @@
                         }
                     }
                     break;
+
+                case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj);
+                        }
+                    }
+                    break;
+
+                case MSG_I_SCO_AUDIO_STATE_CHANGED:
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            mBtHelper.onScoAudioStateChanged(msg.arg1);
+                        }
+                    }
+                    break;
+
                 case MSG_TOGGLE_HDMI:
                     synchronized (mDeviceStateLock) {
                         mDeviceInventory.onToggleHdmi();
@@ -1437,6 +1532,9 @@
     private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
     private static final int MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY = 40;
     private static final int MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY = 41;
+    private static final int MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT = 42;
+    private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
+    private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
 
     private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
     //
@@ -1679,9 +1777,8 @@
             return;
         }
         Log.w(TAG, "Communication client died");
-        setCommunicationRouteForClient(
-                client.getBinder(), client.getPid(), null, BtHelper.SCO_MODE_UNDEFINED,
-                "onCommunicationRouteClientDied");
+        removeCommunicationRouteClient(client.getBinder(), true);
+        onUpdateCommunicationRouteClient("onCommunicationRouteClientDied");
     }
 
     /**
@@ -1735,11 +1832,31 @@
             AudioSystem.setParameters("BT_SCO=on");
         }
         if (preferredCommunicationDevice == null) {
-            postRemovePreferredDevicesForStrategy(mCommunicationStrategyId);
+            removePreferredDevicesForStrategySync(mCommunicationStrategyId);
         } else {
-            postSetPreferredDevicesForStrategy(
+            setPreferredDevicesForStrategySync(
                     mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
         }
+        onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
+    }
+
+    /**
+     * Select new communication device from communication route client at the top of the stack
+     * and restore communication route including restarting SCO audio if needed.
+     */
+    // @GuardedBy("mSetModeLock")
+    @GuardedBy("mDeviceStateLock")
+    private void onUpdateCommunicationRouteClient(String eventSource) {
+        onUpdateCommunicationRoute(eventSource);
+        CommunicationRouteClient crc = topCommunicationRouteClient();
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
+                    + crc + " eventSource: " + eventSource);
+        }
+        if (crc != null) {
+            setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(),
+                    BtHelper.SCO_MODE_UNDEFINED, eventSource);
+        }
     }
 
     private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5aae319..d775581 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1368,14 +1368,18 @@
             mRm = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
             if (mRm != null) {
                 mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
-                updateAssistantUId(true);
+                synchronized (mSettingsLock) {
+                    updateAssistantUIdLocked(/* forceUpdate= */ true);
+                }
             }
         }
 
         @Override
         public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
             if (RoleManager.ROLE_ASSISTANT.equals(roleName)) {
-                updateAssistantUId(false);
+                synchronized (mSettingsLock) {
+                    updateAssistantUIdLocked(/* forceUpdate= */ false);
+                }
             }
         }
 
@@ -1474,7 +1478,7 @@
             sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
             sendEnabledSurroundFormats(mContentResolver, true);
             AudioSystem.setRttEnabled(mRttEnabled);
-            updateAssistantServicesUidsLocked();
+            resetAssistantServicesUidsLocked();
         }
 
         synchronized (mAccessibilityServiceUidsLock) {
@@ -1603,6 +1607,12 @@
     }
 
     @GuardedBy("mSettingsLock")
+    private void resetAssistantServicesUidsLocked() {
+        mAssistantUids.clear();
+        updateAssistantUIdLocked(/* forceUpdate= */ true);
+    }
+
+    @GuardedBy("mSettingsLock")
     private void updateAssistantServicesUidsLocked() {
         int[] assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray();
         AudioSystem.setAssistantServicesUids(assistantUids);
@@ -2386,7 +2396,7 @@
     }
 
     @GuardedBy("mSettingsLock")
-    private void updateAssistantUId(boolean forceUpdate) {
+    private void updateAssistantUIdLocked(boolean forceUpdate) {
         int assistantUid = INVALID_UID;
         // Consider assistants in the following order of priority:
         // 1) apk in assistant role
@@ -2484,7 +2494,7 @@
             readDockAudioSettings(cr);
             sendEncodedSurroundMode(cr, "readPersistedSettings");
             sendEnabledSurroundFormats(cr, true);
-            updateAssistantUId(true);
+            updateAssistantUIdLocked(/* forceUpdate= */ true);
             resetActiveAssistantUidsLocked();
             AudioSystem.setRttEnabled(mRttEnabled);
         }
@@ -8317,7 +8327,7 @@
                 updateMasterBalance(mContentResolver);
                 updateEncodedSurroundOutput();
                 sendEnabledSurroundFormats(mContentResolver, mSurroundModeChanged);
-                updateAssistantUId(false);
+                updateAssistantUIdLocked(/* forceUpdate= */ false);
             }
         }
 
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 49a935e..d10ed55 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -104,7 +104,7 @@
     // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
     /*package*/  static final int SCO_MODE_VIRTUAL_CALL = 0;
     // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
-    private  static final int SCO_MODE_VR = 2;
+    private static final int SCO_MODE_VR = 2;
     // max valid SCO audio mode values
     private static final int SCO_MODE_MAX = 2;
 
@@ -305,69 +305,77 @@
             BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
             setBtScoActiveDevice(btDevice);
         } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-            boolean broadcast = false;
-            int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
             int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-            Log.i(TAG, "receiveBtEvent ACTION_AUDIO_STATE_CHANGED: " + btState);
-            switch (btState) {
-                case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                    scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
-                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
-                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                    } else if (mDeviceBroker.isBluetoothScoRequested()) {
-                        // broadcast intent if the connection was initated by AudioService
-                        broadcast = true;
-                    }
-                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
-                    break;
-                case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                    mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
-                    scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
-                    // 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 BT SCO is requested.
-                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
-                            || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
-                                    && mDeviceBroker.isBluetoothScoRequested())) {
-                        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
-                                && connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                mBluetoothHeadsetDevice, mScoAudioMode)) {
-                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                            scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
-                            broadcast = true;
-                            break;
-                        }
-                    }
-                    if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
-                        broadcast = true;
-                    }
-                    mScoAudioState = SCO_STATE_INACTIVE;
-                    break;
-                case BluetoothHeadset.STATE_AUDIO_CONNECTING:
-                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
-                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                    }
-                    break;
-                default:
-                    break;
-            }
-            if (broadcast) {
-                broadcastScoConnectionState(scoAudioState);
-                //FIXME: this is to maintain compatibility with deprecated intent
-                // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
-                Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
-                newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
-                sendStickyBroadcastToAll(newIntent);
-            }
+            Log.i(TAG,"receiveBtEvent ACTION_AUDIO_STATE_CHANGED: "+btState);
+            mDeviceBroker.postScoAudioStateChanged(btState);
         }
     }
 
     /**
+     * Exclusively called from AudioDeviceBroker when handling MSG_I_SCO_AUDIO_STATE_CHANGED
+     * as part of the serialization of the communication route selection
+     */
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    void onScoAudioStateChanged(int state) {
+        boolean broadcast = false;
+        int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+        switch (state) {
+            case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+                if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                        && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                    mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                } else if (mDeviceBroker.isBluetoothScoRequested()) {
+                    // broadcast intent if the connection was initated by AudioService
+                    broadcast = true;
+                }
+                mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
+                break;
+            case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
+                scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+                // 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 BT SCO is requested.
+                if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+                    if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+                            && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                            mBluetoothHeadsetDevice, mScoAudioMode)) {
+                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                        scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
+                        broadcast = true;
+                        break;
+                    }
+                }
+                if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
+                    broadcast = true;
+                }
+                mScoAudioState = SCO_STATE_INACTIVE;
+                break;
+            case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+                if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                        && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                    mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                }
+                break;
+            default:
+                break;
+        }
+        if(broadcast) {
+            broadcastScoConnectionState(scoAudioState);
+            //FIXME: this is to maintain compatibility with deprecated intent
+            // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+            Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+            newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
+            sendStickyBroadcastToAll(newIntent);
+        }
+
+    }
+    /**
      *
      * @return false if SCO isn't connected
      */
@@ -756,6 +764,15 @@
                 case SCO_STATE_ACTIVE_INTERNAL:
                     Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
                     break;
+                case SCO_STATE_ACTIVE_EXTERNAL:
+                    /* Confirm SCO Audio connection to requesting app as it is already connected
+                     * externally (i.e. through SCO APIs by Telecom service).
+                     * Once SCO Audio is disconnected by the external owner, we will reconnect it
+                     * automatically on behalf of the requesting app and the state will move to
+                     * SCO_STATE_ACTIVE_INTERNAL.
+                     */
+                    broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+                    break;
                 default:
                     Log.w(TAG, "requestScoState: failed to connect in state "
                             + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 4f26809..273afcc 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -42,7 +42,7 @@
     private final int mAutoGroupAtCount;
 
     // count the number of ongoing notifications per group
-    // userId -> (package name -> (group Id -> (set of notification keys)))
+    // userId|packageName -> (set of ongoing notifications that aren't in an app group)
     final ArrayMap<String, ArraySet<String>>
             mOngoingGroupCount = new ArrayMap<>();
 
@@ -55,52 +55,43 @@
         mCallback = callback;
     }
 
-    private String generatePackageGroupKey(int userId, String pkg, String group) {
-        return userId + "|" + pkg + "|" + group;
+    private String generatePackageKey(int userId, String pkg) {
+        return userId + "|" + pkg;
     }
 
     @VisibleForTesting
-    protected int getOngoingGroupCount(int userId, String pkg, String group) {
-        String key = generatePackageGroupKey(userId, pkg, group);
+    protected int getOngoingGroupCount(int userId, String pkg) {
+        String key = generatePackageKey(userId, pkg);
         return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
     }
 
-    private void addToOngoingGroupCount(StatusBarNotification sbn, boolean add) {
-        if (sbn.getNotification().isGroupSummary()) return;
-        if (!sbn.isOngoing() && add) return;
-        String group = sbn.getGroup();
-        if (group == null) return;
-        int userId = sbn.getUser().getIdentifier();
-        String key = generatePackageGroupKey(userId, sbn.getPackageName(), group);
+    private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) {
+        if (sbn.getNotification().isGroupSummary()) {
+            return;
+        }
+        String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
         ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
         if (add) {
             notifications.add(sbn.getKey());
             mOngoingGroupCount.put(key, notifications);
         } else {
             notifications.remove(sbn.getKey());
-            // we dont need to put it back if it is default
+            // we don't need to put it back if it is default
         }
-        String combinedKey = generatePackageGroupKey(userId, sbn.getPackageName(), group);
+
         boolean needsOngoingFlag = notifications.size() > 0;
-        mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), needsOngoingFlag);
+        mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag);
     }
 
-    public void onNotificationUpdated(StatusBarNotification childSbn,
-            boolean autogroupSummaryExists) {
-        if (childSbn.getGroup() != AUTOGROUP_KEY
-                || childSbn.getNotification().isGroupSummary()) return;
-        if (childSbn.isOngoing()) {
-            addToOngoingGroupCount(childSbn, true);
-        } else {
-            addToOngoingGroupCount(childSbn, false);
-        }
+    public void onNotificationUpdated(StatusBarNotification childSbn) {
+        updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup());
     }
 
     public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
-        if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
         try {
+            updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup());
+
             List<String> notificationsToGroup = new ArrayList<>();
-            if (autogroupSummaryExists) addToOngoingGroupCount(sbn, true);
             if (!sbn.isAppGroup()) {
                 // Not grouped by the app, add to the list of notifications for the app;
                 // send grouping update if app exceeds the autogrouping limit.
@@ -134,6 +125,7 @@
                 // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
                 maybeUngroup(sbn, false, sbn.getUserId());
             }
+
         } catch (Exception e) {
             Slog.e(TAG, "Failure processing new notification", e);
         }
@@ -141,7 +133,7 @@
 
     public void onNotificationRemoved(StatusBarNotification sbn) {
         try {
-            addToOngoingGroupCount(sbn, false);
+            updateOngoingGroupCount(sbn, false);
             maybeUngroup(sbn, true, sbn.getUserId());
         } catch (Exception e) {
             Slog.e(TAG, "Error processing canceled notification", e);
@@ -189,7 +181,8 @@
     private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
             boolean summaryNeeded) {
         if (summaryNeeded) {
-            mCallback.addAutoGroupSummary(userId, packageName, triggeringKey);
+            mCallback.addAutoGroupSummary(userId, packageName, triggeringKey,
+                    getOngoingGroupCount(userId, packageName) > 0);
         } else {
             mCallback.removeAutoGroupSummary(userId, packageName);
         }
@@ -209,7 +202,8 @@
     protected interface Callback {
         void addAutoGroup(String key);
         void removeAutoGroup(String key);
-        void addAutoGroupSummary(int userId, String pkg, String triggeringKey);
+        void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
+                boolean needsOngoingFlag);
         void removeAutoGroupSummary(int user, String pkg);
         void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag);
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5d1c3e1..326a5f2 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2557,8 +2557,10 @@
             }
 
             @Override
-            public void addAutoGroupSummary(int userId, String pkg, String triggeringKey) {
-                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey);
+            public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
+                    boolean needsOngoingFlag) {
+                NotificationRecord r = createAutoGroupSummary(
+                        userId, pkg, triggeringKey, needsOngoingFlag);
                 if (r != null) {
                     final boolean isAppForeground =
                             mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
@@ -5739,6 +5741,7 @@
     void removeAutogroupKeyLocked(String key) {
         NotificationRecord r = mNotificationsByKey.get(key);
         if (r == null) {
+            Slog.w(TAG, "Failed to remove autogroup " + key);
             return;
         }
         if (r.getSbn().getOverrideGroupKey() != null) {
@@ -5778,7 +5781,8 @@
     }
 
     // Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
-    NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey) {
+    NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
+            boolean needsOngoingFlag) {
         NotificationRecord summaryRecord = null;
         boolean isPermissionFixed = mPermissionHelper.isMigrationEnabled()
                 ? mPermissionHelper.isPermissionFixed(pkg, userId) : false;
@@ -5818,6 +5822,7 @@
                                 .setGroup(GroupHelper.AUTOGROUP_KEY)
                                 .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
                                 .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
+                                .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag)
                                 .setColor(adjustedSbn.getNotification().color)
                                 .setLocalOnly(true)
                                 .build();
@@ -7356,17 +7361,16 @@
                         mListeners.notifyPostedLocked(r, old);
                         if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
                                 && !isCritical(r)) {
-                            mHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
+                            mHandler.post(() -> {
+                                synchronized (mNotificationLock) {
                                     mGroupHelper.onNotificationPosted(
                                             n, hasAutoGroupSummaryLocked(n));
                                 }
                             });
                         } else if (oldSbn != null) {
                             final NotificationRecord finalRecord = r;
-                            mHandler.post(() -> mGroupHelper.onNotificationUpdated(
-                                    finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));
+                            mHandler.post(() ->
+                                    mGroupHelper.onNotificationUpdated(finalRecord.getSbn()));
                         }
                     } else {
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1b0b492..b20781f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3962,8 +3962,8 @@
 
         try {
             mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
-                    forceRelayout, alwaysConsumeSystemBars, displayId, Integer.MAX_VALUE,
-                    resizeMode);
+                    forceRelayout, alwaysConsumeSystemBars, displayId, mSyncSeqId, resizeMode);
+
             if (drawPending && reportOrientation && mOrientationChanging) {
                 mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 55e504a..486b6a1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7233,7 +7233,7 @@
             return;
         }
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.SEND_LOST_MODE_LOCATION_UPDATES));
+                hasCallingOrSelfPermission(permission.TRIGGER_LOST_MODE));
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index e096687..4a51e41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -217,6 +217,8 @@
     @Mock
     private AppOpsManager mAppOpsManager;
     @Mock
+    private BatteryManager mBatteryManager;
+    @Mock
     private DeviceIdleInternal mDeviceIdleInternal;
     @Mock
     private PermissionManagerServiceInternal mPermissionManagerInternal;
@@ -453,6 +455,7 @@
                         eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString()));
 
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+        when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager);
 
         registerAppIds(new String[]{TEST_CALLING_PACKAGE},
                 new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)});
@@ -477,6 +480,8 @@
 
         // Other boot phases don't matter
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        verify(mBatteryManager).isCharging();
         setTareEnabled(false);
         mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
         mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW;
@@ -1101,6 +1106,7 @@
                 new Intent(parole ? BatteryManager.ACTION_CHARGING
                         : BatteryManager.ACTION_DISCHARGING));
         assertAndHandleMessageSync(CHARGING_STATUS_CHANGED);
+        assertEquals(parole, mService.mAppStandbyParole);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 484dc84..197c21f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -8455,7 +8455,7 @@
 
     @Test
     public void testSendLostModeLocationUpdate_notOrganizationOwnedDevice() {
-        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        mContext.callerPermissions.add(permission.TRIGGER_LOST_MODE);
         assertThrows(IllegalStateException.class, () -> dpm.sendLostModeLocationUpdate(
                 getServices().executor, /* empty callback */ result -> {}));
     }
@@ -8463,7 +8463,7 @@
     @Test
     public void testSendLostModeLocationUpdate_asDeviceOwner() throws Exception {
         final String TEST_PROVIDER = "network";
-        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        mContext.callerPermissions.add(permission.TRIGGER_LOST_MODE);
         setDeviceOwner();
         when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
         when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
@@ -8480,7 +8480,7 @@
         final int MANAGED_PROFILE_ADMIN_UID =
                 UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
         mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        mContext.callerPermissions.add(permission.TRIGGER_LOST_MODE);
         addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
         when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 5458a5b..ff6c976 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
@@ -53,7 +54,7 @@
 public class GroupHelperTest extends UiServiceTestCase {
     private @Mock GroupHelper.Callback mCallback;
 
-    private final static int AUTOGROUP_AT_COUNT = 4;
+    private final static int AUTOGROUP_AT_COUNT = 7;
     private GroupHelper mGroupHelper;
 
     @Before
@@ -88,7 +89,7 @@
                     false);
         }
         verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString());
+                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
         verify(mCallback, never()).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -105,7 +106,7 @@
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
         verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString());
+                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
         verify(mCallback, never()).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -120,7 +121,8 @@
         }
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.ALL), false);
-        verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+        verify(mCallback, never()).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), anyBoolean());
         verify(mCallback, never()).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -136,13 +138,12 @@
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false);
         verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString());
+                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
         verify(mCallback, never()).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
     }
 
-
     @Test
     public void testPostingOverLimit() throws Exception {
         final String pkg = "package";
@@ -150,7 +151,23 @@
             mGroupHelper.onNotificationPosted(
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+    }
+
+    @Test
+    public void testPostingOverLimit_addsOngoingFlag() throws Exception {
+        final String pkg = "package";
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+            }
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(true));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -178,7 +195,7 @@
 
         int userId = UserHandle.SYSTEM.getIdentifier();
         assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT + 1);
+                userId, pkg), AUTOGROUP_AT_COUNT + 1);
     }
 
     @Test
@@ -199,15 +216,14 @@
         }
 
         notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
-
-        mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+        mGroupHelper.onNotificationUpdated(notifications.get(0));
 
         verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
                 .updateAutogroupSummary(anyInt(), anyString(), eq(true));
 
         int userId = UserHandle.SYSTEM.getIdentifier();
         assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT);
+                userId, pkg), AUTOGROUP_AT_COUNT);
     }
 
     @Test
@@ -229,18 +245,18 @@
 
         notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
 
-        mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+        mGroupHelper.onNotificationUpdated(notifications.get(0));
 
         notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
 
-        mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+        mGroupHelper.onNotificationUpdated(notifications.get(0));
 
         verify(mCallback, times(AUTOGROUP_AT_COUNT + 3))
                 .updateAutogroupSummary(anyInt(), anyString(), eq(true));
 
         int userId = UserHandle.SYSTEM.getIdentifier();
         assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT + 1);
+                userId, pkg), AUTOGROUP_AT_COUNT + 1);
     }
 
     @Test
@@ -267,7 +283,7 @@
 
         int userId = UserHandle.SYSTEM.getIdentifier();
         assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT);
+                userId, pkg), AUTOGROUP_AT_COUNT);
     }
 
 
@@ -288,14 +304,14 @@
         }
 
         notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+        mGroupHelper.onNotificationUpdated(notifications.get(0));
 
         verify(mCallback, times(1))
                 .updateAutogroupSummary(anyInt(), anyString(), eq(true));
 
         int userId = UserHandle.SYSTEM.getIdentifier();
         assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg, AUTOGROUP_KEY), 1);
+                userId, pkg), 1);
     }
 
     @Test
@@ -305,7 +321,7 @@
         for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
             notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
         }
-        StatusBarNotification sbn = notifications.get(0);
+        StatusBarNotification sbn = notifications.get(AUTOGROUP_AT_COUNT);
         sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
         sbn.setOverrideGroupKey(AUTOGROUP_KEY);
 
@@ -319,7 +335,7 @@
 
         int userId = UserHandle.SYSTEM.getIdentifier();
         assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg, AUTOGROUP_KEY), 1);
+                userId, pkg), 1);
     }
 
     @Test
@@ -342,7 +358,7 @@
                 .updateAutogroupSummary(anyInt(), anyString(), eq(true));
 
         int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg, AUTOGROUP_KEY), 0);
+        assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg), 0);
     }
 
 
@@ -355,7 +371,7 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -382,28 +398,22 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), anyBoolean());
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
         Mockito.reset(mCallback);
 
-        int i = 0;
-        for (i = 0; i < AUTOGROUP_AT_COUNT - 2; i++) {
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             final StatusBarNotification sbn =
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
             mGroupHelper.onNotificationPosted(sbn, false);
+            verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
+            if (i < AUTOGROUP_AT_COUNT -1) {
+                verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+            }
         }
-        verify(mCallback, times(AUTOGROUP_AT_COUNT - 2)).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        Mockito.reset(mCallback);
-
-        for (; i < AUTOGROUP_AT_COUNT; i++) {
-            final StatusBarNotification sbn =
-                    getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
-            mGroupHelper.onNotificationPosted(sbn, false);
-        }
-        verify(mCallback, times(2)).removeAutoGroup(anyString());
         verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
     }
 
@@ -417,7 +427,7 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -441,7 +451,7 @@
         final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM);
         posted.add(sbn);
         mGroupHelper.onNotificationPosted(sbn, true);
-        verify(mCallback, times(posted.size())).addAutoGroup(anyString());
+        verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 2ba587d..0f6d5a5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -722,7 +722,7 @@
         when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
 
         NotificationRecord r = mService.createAutoGroupSummary(
-                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey());
+                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false);
 
         assertThat(r.isImportanceFixed()).isTrue();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 66da2a6..716612c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -284,7 +284,7 @@
                 .setCreateActivity(true).build().getTopMostActivity();
         activity2.getTask().setResumedActivity(activity2, "test");
 
-        mAtm.mAmInternal.deletePendingTopUid(activity1.getUid());
+        mAtm.mAmInternal.deletePendingTopUid(activity1.getUid(), Long.MAX_VALUE);
         clearInvocations(mAtm);
         activity1.moveFocusableActivityToTop("test");
         assertTrue(mAtm.mAmInternal.isPendingTopUid(activity1.getUid()));
diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
index 0fa79df..04c52f7 100644
--- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
@@ -84,7 +84,7 @@
     private final Object mLock = new Object();
     private boolean mIsOpen;
 
-    private final UsbMidiPacketConverter mUsbMidiPacketConverter = new UsbMidiPacketConverter();
+    private UsbMidiPacketConverter mUsbMidiPacketConverter;
 
     private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
 
@@ -264,6 +264,11 @@
         Log.d(TAG, "openLocked()");
         UsbManager manager = mContext.getSystemService(UsbManager.class);
 
+        // Converting from raw MIDI to USB MIDI is not thread-safe.
+        // UsbMidiPacketConverter creates a converter from raw MIDI
+        // to USB MIDI for each USB output.
+        mUsbMidiPacketConverter = new UsbMidiPacketConverter(mNumOutputs);
+
         mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size());
         mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size());
         mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size());
@@ -415,7 +420,7 @@
                             } else {
                                 convertedArray =
                                         mUsbMidiPacketConverter.rawMidiToUsbMidi(
-                                                 event.data, event.count);
+                                                 event.data, event.count, portFinal);
                             }
 
                             if (DEBUG) {
@@ -518,6 +523,8 @@
         mInputUsbEndpoints = null;
         mOutputUsbEndpoints = null;
 
+        mUsbMidiPacketConverter = null;
+
         mIsOpen = false;
     }
 
diff --git a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
index 7c93c76..56bb236 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
@@ -74,8 +74,15 @@
     private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
     private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
 
-    private UsbMidiEncoder mUsbMidiEncoder = new UsbMidiEncoder();
     private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
+    private UsbMidiEncoder[] mUsbMidiEncoders;
+
+    public UsbMidiPacketConverter(int numEncoders) {
+        mUsbMidiEncoders = new UsbMidiEncoder[numEncoders];
+        for (int i = 0; i < numEncoders; i++) {
+            mUsbMidiEncoders[i] = new UsbMidiEncoder();
+        }
+    }
 
     /**
      * Converts a USB MIDI array into a raw MIDI array.
@@ -93,10 +100,11 @@
      *
      * @param midiBytes the raw MIDI bytes to convert
      * @param size the size of usbMidiBytes
+     * @param encoderId which encoder to use
      * @return byte array of USB MIDI packets
      */
-    public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size) {
-        return mUsbMidiEncoder.encode(midiBytes, size);
+    public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId) {
+        return mUsbMidiEncoders[encoderId].encode(midiBytes, size);
     }
 
     private class UsbMidiDecoder {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index d527a23..c86f38d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -679,6 +679,7 @@
     private void restartProcessLocked() {
         Slog.v(TAG, "Restarting hotword detection process");
         ServiceConnection oldConnection = mRemoteHotwordDetectionService;
+        HotwordDetectionServiceIdentity previousIdentity = mIdentity;
 
         // TODO(volnov): this can be done after connect() has been successful.
         if (mValidatingDspTrigger) {
@@ -722,6 +723,9 @@
         }
         oldConnection.ignoreConnectionStatusEvents();
         oldConnection.unbind();
+        if (previousIdentity != null) {
+            removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
+        }
     }
 
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index 730a9d1..2d0135a 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -383,8 +383,6 @@
      * activity.
      */
     public @NonNull ModemActivityInfo getDelta(@NonNull ModemActivityInfo other) {
-        int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
-
         ActivityStatsTechSpecificInfo[] mDeltaSpecificInfo;
         mDeltaSpecificInfo = new ActivityStatsTechSpecificInfo[other.getSpecificInfoLength()];
 
@@ -399,6 +397,7 @@
                         if (other.mActivityStatsTechSpecificInfo[i].getFrequencyRange()
                                 == mActivityStatsTechSpecificInfo[j].getFrequencyRange()) {
                             int freq = mActivityStatsTechSpecificInfo[j].getFrequencyRange();
+                            int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
                             for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
                                 txTimeMs[lvl] =
                                         (int) (other.getTransmitDurationMillisAtPowerLevel(
@@ -416,6 +415,7 @@
                                                         - getReceiveTimeMillis(rat, freq)));
                         }
                     } else {
+                        int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
                         for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
                             txTimeMs[lvl] =
                                     (int) (other.getTransmitDurationMillisAtPowerLevel(lvl, rat)