Merge "Don't check height if activity bounds empty" into tm-qpr-dev
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 8ee23aa..aba4b2c0 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -188,6 +188,14 @@
     public int launchIntoPipHostTaskId;
 
     /**
+     * The task id of the parent Task of the launch-into-pip Activity, i.e., if task have more than
+     * one activity it will create new task for this activity, this id is the origin task id and
+     * the pip activity will be reparent to origin task when it exit pip mode.
+     * @hide
+     */
+    public int lastParentTaskIdBeforePip;
+
+    /**
      * The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of
      * (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS),
      * {@code null} otherwise.
@@ -503,6 +511,7 @@
         pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
         shouldDockBigOverlays = source.readBoolean();
         launchIntoPipHostTaskId = source.readInt();
+        lastParentTaskIdBeforePip = source.readInt();
         displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
         topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
         isResizeable = source.readBoolean();
@@ -549,6 +558,7 @@
         dest.writeTypedObject(pictureInPictureParams, flags);
         dest.writeBoolean(shouldDockBigOverlays);
         dest.writeInt(launchIntoPipHostTaskId);
+        dest.writeInt(lastParentTaskIdBeforePip);
         dest.writeTypedObject(displayCutoutInsets, flags);
         dest.writeTypedObject(topActivityInfo, flags);
         dest.writeBoolean(isResizeable);
@@ -589,6 +599,7 @@
                 + " pictureInPictureParams=" + pictureInPictureParams
                 + " shouldDockBigOverlays=" + shouldDockBigOverlays
                 + " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+                + " lastParentTaskIdBeforePip=" + lastParentTaskIdBeforePip
                 + " displayCutoutSafeInsets=" + displayCutoutInsets
                 + " topActivityInfo=" + topActivityInfo
                 + " launchCookies=" + launchCookies
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 34d016a..7c54a9b 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -36,4 +36,5 @@
     int surfaceGroupId;
     String physicalCameraId;
     List<CameraOutputConfig> sharedSurfaceConfigs;
+    boolean isMultiResolutionOutput;
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index c8dc2d0..77def20 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -226,6 +226,9 @@
             OutputConfiguration cameraOutput = new OutputConfiguration(output.surfaceGroupId,
                     outputSurface);
 
+            if (output.isMultiResolutionOutput) {
+                cameraOutput.setMultiResolutionOutput();
+            }
             if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) {
                 cameraOutput.enableSurfaceSharing();
                 for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 90e92db..9868d87 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -421,7 +421,7 @@
      *         call, or no non-negative group ID has been set.
      * @hide
      */
-    void setMultiResolutionOutput() {
+    public void setMultiResolutionOutput() {
         if (mIsShared) {
             throw new IllegalStateException("Multi-resolution output flag must not be set for " +
                     "configuration with surface sharing");
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 76475f2..4f49f12 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2808,6 +2808,15 @@
     public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
 
     /**
+     * Returns the battery consumption (in microcoulombs) of the phone calls, derived from on device
+     * power measurement data.
+     * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+     *
+     * {@hide}
+     */
+    public abstract long getPhoneEnergyConsumptionUC();
+
+    /**
      * Returns the battery consumption (in microcoulombs) of the screen while on, derived from on
      * device power measurement data.
      * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 36e0dc3..b02e123 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -141,12 +141,15 @@
     private int mRingerMode;
     private int mZenMode;
     private boolean mPlaySample;
+    private final boolean mDeviceHasProductStrategies;
 
     private static final int MSG_SET_STREAM_VOLUME = 0;
     private static final int MSG_START_SAMPLE = 1;
     private static final int MSG_STOP_SAMPLE = 2;
     private static final int MSG_INIT_SAMPLE = 3;
+    private static final int MSG_UPDATE_SLIDER_MAYBE_LATER = 4;
     private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
+    private static final int CHECK_UPDATE_SLIDER_LATER_MS = 500;
     private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
     private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
     private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000);
@@ -170,6 +173,7 @@
             boolean playSample) {
         mContext = context;
         mAudioManager = context.getSystemService(AudioManager.class);
+        mDeviceHasProductStrategies = hasAudioProductStrategies();
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mNotificationPolicy = mNotificationManager.getConsolidatedNotificationPolicy();
         mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
@@ -186,7 +190,7 @@
         }
         mZenMode = mNotificationManager.getZenMode();
 
-        if (hasAudioProductStrategies()) {
+        if (mDeviceHasProductStrategies) {
             mVolumeGroupId = getVolumeGroupIdForLegacyStreamType(mStreamType);
             mAttributes = getAudioAttributesForLegacyStreamType(
                     mStreamType);
@@ -213,6 +217,12 @@
         mDefaultUri = defaultUri;
     }
 
+    /**
+     * DO NOT CALL every time this is needed, use once in constructor,
+     * read mDeviceHasProductStrategies instead
+     * @return true if stream types are used for volume management, false if volume groups are
+     *     used for volume management
+     */
     private boolean hasAudioProductStrategies() {
         return AudioManager.getAudioProductStrategies().size() > 0;
     }
@@ -330,6 +340,9 @@
                     onInitSample();
                 }
                 break;
+            case MSG_UPDATE_SLIDER_MAYBE_LATER:
+                onUpdateSliderMaybeLater();
+                break;
             default:
                 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
         }
@@ -353,6 +366,21 @@
                         : isDelay() ? START_SAMPLE_DELAY_MS : 0);
     }
 
+    private void onUpdateSliderMaybeLater() {
+        if (isDelay()) {
+            postUpdateSliderMaybeLater();
+            return;
+        }
+        updateSlider();
+    }
+
+    private void postUpdateSliderMaybeLater() {
+        if (mHandler == null) return;
+        mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SLIDER_MAYBE_LATER),
+                CHECK_UPDATE_SLIDER_LATER_MS);
+    }
+
     // After stop volume it needs to add a small delay when playing volume or set stream.
     // It is because the call volume is from the earpiece and the alarm/ring/media
     // is from the speaker. If play the alarm volume or set alarm stream right after stop
@@ -422,7 +450,7 @@
         postStopSample();
         mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
         mReceiver.setListening(false);
-        if (hasAudioProductStrategies()) {
+        if (mDeviceHasProductStrategies) {
             unregisterVolumeGroupCb();
         }
         mSeekBar.setOnSeekBarChangeListener(null);
@@ -442,7 +470,7 @@
                 System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]),
                 false, mVolumeObserver);
         mReceiver.setListening(true);
-        if (hasAudioProductStrategies()) {
+        if (mDeviceHasProductStrategies) {
             registerVolumeGroupCb();
         }
     }
@@ -466,6 +494,7 @@
         mLastProgress = progress;
         mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
         mHandler.removeMessages(MSG_START_SAMPLE);
+        mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME),
                 isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0);
     }
@@ -608,7 +637,7 @@
             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
-                if (hasAudioProductStrategies() && !isDelay()) {
+                if (mDeviceHasProductStrategies && !isDelay()) {
                     updateVolumeSlider(streamType, streamValue);
                 }
             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
@@ -620,9 +649,16 @@
                 }
             } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
-                if (hasAudioProductStrategies() && !isDelay()) {
-                    int streamVolume = mAudioManager.getStreamVolume(streamType);
-                    updateVolumeSlider(streamType, streamVolume);
+
+                if (mDeviceHasProductStrategies) {
+                    if (isDelay()) {
+                        // not the right time to update the sliders, try again later
+                        postUpdateSliderMaybeLater();
+                    } else {
+                        int streamVolume = mAudioManager.getStreamVolume(streamType);
+                        updateVolumeSlider(streamType, streamVolume);
+                    }
+
                 } else {
                     int volumeGroup = getVolumeGroupIdForLegacyStreamType(streamType);
                     if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 8f76a18..2887d1f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -61,6 +61,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -96,6 +97,7 @@
 import android.view.WindowManagerGlobal;
 import android.window.ClientWindowFrames;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.view.BaseIWindow;
@@ -104,9 +106,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -166,11 +169,12 @@
     private static final int MSG_RESIZE_PREVIEW = 10110;
     private static final int MSG_REPORT_SHOWN = 10150;
     private static final int MSG_UPDATE_DIMMING = 10200;
-    private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
-            Float.NEGATIVE_INFINITY);
 
+    /** limit calls to {@link Engine#onComputeColors} to at most once per second */
     private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
-    private static final int PROCESS_LOCAL_COLORS_INTERVAL_MS = 1000;
+
+    /** limit calls to {@link Engine#processLocalColorsInternal} to at most once per 2 seconds */
+    private static final int PROCESS_LOCAL_COLORS_INTERVAL_MS = 2000;
 
     private static final boolean ENABLE_WALLPAPER_DIMMING =
             SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true);
@@ -180,6 +184,9 @@
     private final ArrayList<Engine> mActiveEngines
             = new ArrayList<Engine>();
 
+    private Handler mBackgroundHandler;
+    private HandlerThread mBackgroundThread;
+
     static final class WallpaperCommand {
         String action;
         int x;
@@ -198,14 +205,6 @@
      */
     public class Engine {
         IWallpaperEngineWrapper mIWallpaperEngine;
-        final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
-        final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
-
-        // 2D matrix [x][y] to represent a page of a portion of a window
-        EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
-        Bitmap mLastScreenshot;
-        int mLastWindowPage = -1;
-        private boolean mResetWindowPages;
 
         // Copies from mIWallpaperEngine.
         HandlerCaller mCaller;
@@ -267,11 +266,34 @@
 
         final Object mLock = new Object();
         boolean mOffsetMessageEnqueued;
+
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-        float mPendingXOffset;
-        float mPendingYOffset;
-        float mPendingXOffsetStep;
-        float mPendingYOffsetStep;
+        @GuardedBy("mLock")
+        private float mPendingXOffset;
+        @GuardedBy("mLock")
+        private float mPendingYOffset;
+        @GuardedBy("mLock")
+        private float mPendingXOffsetStep;
+        @GuardedBy("mLock")
+        private float mPendingYOffsetStep;
+
+        /**
+         * local color extraction related fields. When a user calls `addLocalColorAreas`
+         */
+        @GuardedBy("mLock")
+        private final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
+
+        @GuardedBy("mLock")
+        private final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
+        private long mLastProcessLocalColorsTimestamp;
+        private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false);
+        private int mPixelCopyCount = 0;
+        // 2D matrix [x][y] to represent a page of a portion of a window
+        @GuardedBy("mLock")
+        private EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
+        private Bitmap mLastScreenshot;
+        private boolean mResetWindowPages;
+
         boolean mPendingSync;
         MotionEvent mPendingMove;
         boolean mIsInAmbientMode;
@@ -280,12 +302,8 @@
         private long mLastColorInvalidation;
         private final Runnable mNotifyColorsChanged = this::notifyColorsChanged;
 
-        // used to throttle processLocalColors
-        private long mLastProcessLocalColorsTimestamp;
-        private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false);
         private final Supplier<Long> mClockFunction;
         private final Handler mHandler;
-
         private Display mDisplay;
         private Context mDisplayContext;
         private int mDisplayState;
@@ -825,7 +843,7 @@
                             + "was not established.");
                 }
                 mResetWindowPages = true;
-                processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+                processLocalColors();
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
             }
@@ -1361,10 +1379,9 @@
                         mIsCreating = false;
                         mSurfaceCreated = true;
                         if (redrawNeeded) {
-                            resetWindowPages();
                             mSession.finishDrawing(mWindow, null /* postDrawTransaction */,
                                                    Integer.MAX_VALUE);
-                            processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+                            processLocalColors();
                         }
                         reposition();
                         reportEngineShown(shouldWaitForEngineShown());
@@ -1509,7 +1526,7 @@
             if (!mDestroyed) {
                 mVisible = visible;
                 reportVisibility();
-                if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+                if (mReportedVisible) processLocalColors();
             } else {
                 AnimationHandler.requestAnimatorsEnabled(visible, this);
             }
@@ -1594,14 +1611,14 @@
             }
 
             // setup local color extraction data
-            processLocalColors(xOffset, xOffsetStep);
+            processLocalColors();
         }
 
         /**
          * Thread-safe util to call {@link #processLocalColorsInternal} with a minimum interval of
          * {@link #PROCESS_LOCAL_COLORS_INTERVAL_MS} between two calls.
          */
-        private void processLocalColors(float xOffset, float xOffsetStep) {
+        private void processLocalColors() {
             if (mProcessLocalColorsPending.compareAndSet(false, true)) {
                 final long now = mClockFunction.get();
                 final long timeSinceLastColorProcess = now - mLastProcessLocalColorsTimestamp;
@@ -1611,80 +1628,98 @@
                 mHandler.postDelayed(() -> {
                     mLastProcessLocalColorsTimestamp = now + timeToWait;
                     mProcessLocalColorsPending.set(false);
-                    processLocalColorsInternal(xOffset, xOffsetStep);
+                    processLocalColorsInternal();
                 }, timeToWait);
             }
         }
 
-        private void processLocalColorsInternal(float xOffset, float xOffsetStep) {
-            // implemented by the wallpaper
+        /**
+         * Default implementation of the local color extraction.
+         * This will take a screenshot of the whole wallpaper on the main thread.
+         * Then, in a background thread, for each launcher page, for each area that needs color
+         * extraction in this page, creates a sub-bitmap and call {@link WallpaperColors#fromBitmap}
+         * to extract the colors. Every time a launcher page has been processed, call
+         * {@link #notifyLocalColorsChanged} with the color and areas of this page.
+         */
+        private void processLocalColorsInternal() {
             if (supportsLocalColorExtraction()) return;
-            if (DEBUG) {
-                Log.d(TAG, "processLocalColors " + xOffset + " of step "
-                        + xOffsetStep);
-            }
-            //below is the default implementation
-            if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN
-                    || !mSurfaceHolder.getSurface().isValid()) return;
-            int xCurrentPage;
+            float xOffset;
+            float xOffsetStep;
+            float wallpaperDimAmount;
+            int xPage;
             int xPages;
-            if (!validStep(xOffsetStep)) {
-                if (DEBUG) {
-                    Log.w(TAG, "invalid offset step " + xOffsetStep);
-                }
-                xOffset = 0;
-                xOffsetStep = 1;
-                xCurrentPage = 0;
-                xPages = 1;
-            } else {
-                xPages = Math.round(1 / xOffsetStep) + 1;
-                xOffsetStep = (float) 1 / (float) xPages;
-                float shrink = (float) (xPages - 1) / (float) xPages;
-                xOffset *= shrink;
-                xCurrentPage = Math.round(xOffset / xOffsetStep);
-            }
-            if (DEBUG) {
-                Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage);
-                Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
-            }
-
-            float finalXOffsetStep = xOffsetStep;
-            float finalXOffset = xOffset;
-
-            Trace.beginSection("WallpaperService#processLocalColors");
-            resetWindowPages();
-            int xPage = xCurrentPage;
+            Set<RectF> areas;
             EngineWindowPage current;
-            if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
-                mWindowPages = new EngineWindowPage[xPages];
-                initWindowPages(mWindowPages, finalXOffsetStep);
-            }
-            if (mLocalColorsToAdd.size() != 0) {
-                for (RectF colorArea : mLocalColorsToAdd) {
-                    if (!isValid(colorArea)) continue;
-                    mLocalColorAreas.add(colorArea);
-                    int colorPage = getRectFPage(colorArea, finalXOffsetStep);
-                    EngineWindowPage currentPage = mWindowPages[colorPage];
-                    currentPage.setLastUpdateTime(0);
-                    currentPage.removeColor(colorArea);
-                }
-                mLocalColorsToAdd.clear();
-            }
-            if (xPage >= mWindowPages.length) {
+
+            synchronized (mLock) {
+                xOffset = mPendingXOffset;
+                xOffsetStep = mPendingXOffsetStep;
+                wallpaperDimAmount = mWallpaperDimAmount;
+
                 if (DEBUG) {
-                    Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
-                    Log.e(TAG, "error on page " + xPage + " out of " + xPages);
-                    Log.e(TAG,
-                            "error on xOffsetStep " + finalXOffsetStep
-                                    + " xOffset " + finalXOffset);
+                    Log.d(TAG, "processLocalColors " + xOffset + " of step "
+                            + xOffsetStep);
                 }
-                xPage = mWindowPages.length - 1;
+                if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN
+                        || !mSurfaceHolder.getSurface().isValid()) return;
+                int xCurrentPage;
+                if (!validStep(xOffsetStep)) {
+                    if (DEBUG) {
+                        Log.w(TAG, "invalid offset step " + xOffsetStep);
+                    }
+                    xOffset = 0;
+                    xOffsetStep = 1;
+                    xCurrentPage = 0;
+                    xPages = 1;
+                } else {
+                    xPages = Math.round(1 / xOffsetStep) + 1;
+                    xOffsetStep = (float) 1 / (float) xPages;
+                    float shrink = (float) (xPages - 1) / (float) xPages;
+                    xOffset *= shrink;
+                    xCurrentPage = Math.round(xOffset / xOffsetStep);
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage);
+                    Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+                }
+
+                float finalXOffsetStep = xOffsetStep;
+                float finalXOffset = xOffset;
+
+                resetWindowPages();
+                xPage = xCurrentPage;
+                if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
+                    mWindowPages = new EngineWindowPage[xPages];
+                    initWindowPages(mWindowPages, finalXOffsetStep);
+                }
+                if (mLocalColorsToAdd.size() != 0) {
+                    for (RectF colorArea : mLocalColorsToAdd) {
+                        if (!isValid(colorArea)) continue;
+                        mLocalColorAreas.add(colorArea);
+                        int colorPage = getRectFPage(colorArea, finalXOffsetStep);
+                        EngineWindowPage currentPage = mWindowPages[colorPage];
+                        currentPage.setLastUpdateTime(0);
+                        currentPage.removeColor(colorArea);
+                    }
+                    mLocalColorsToAdd.clear();
+                }
+                if (xPage >= mWindowPages.length) {
+                    if (DEBUG) {
+                        Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
+                        Log.e(TAG, "error on page " + xPage + " out of " + xPages);
+                        Log.e(TAG,
+                                "error on xOffsetStep " + finalXOffsetStep
+                                        + " xOffset " + finalXOffset);
+                    }
+                    xPage = mWindowPages.length - 1;
+                }
+                current = mWindowPages[xPage];
+                areas = new HashSet<>(current.getAreas());
             }
-            current = mWindowPages[xPage];
-            updatePage(current, xPage, xPages, finalXOffsetStep);
-            Trace.endSection();
+            updatePage(current, areas, xPage, xPages, wallpaperDimAmount);
         }
 
+        @GuardedBy("mLock")
         private void initWindowPages(EngineWindowPage[] windowPages, float step) {
             for (int i = 0; i < windowPages.length; i++) {
                 windowPages[i] = new EngineWindowPage();
@@ -1701,16 +1736,16 @@
             }
         }
 
-        void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
-                float xOffsetStep) {
+        void updatePage(EngineWindowPage currentPage, Set<RectF> areas, int pageIndx, int numPages,
+                float wallpaperDimAmount) {
+
             // in case the clock is zero, we start with negative time
             long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION;
             long lapsed = current - currentPage.getLastUpdateTime();
             // Always update the page when the last update time is <= 0
             // This is important especially when the device first boots
-            if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) {
-                return;
-            }
+            if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+
             Surface surface = mSurfaceHolder.getSurface();
             if (!surface.isValid()) return;
             boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
@@ -1723,43 +1758,59 @@
                 Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height);
                 return;
             }
+            final String pixelCopySectionName = "WallpaperService#pixelCopy";
+            final int pixelCopyCount = mPixelCopyCount++;
+            Trace.beginAsyncSection(pixelCopySectionName, pixelCopyCount);
             Bitmap screenShot = Bitmap.createBitmap(width, height,
                     Bitmap.Config.ARGB_8888);
             final Bitmap finalScreenShot = screenShot;
-            Trace.beginSection("WallpaperService#pixelCopy");
-            PixelCopy.request(surface, screenShot, (res) -> {
-                Trace.endSection();
-                if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
-                if (res != PixelCopy.SUCCESS) {
-                    Bitmap lastBitmap = currentPage.getBitmap();
-                    // assign the last bitmap taken for now
-                    currentPage.setBitmap(mLastScreenshot);
-                    Bitmap lastScreenshot = mLastScreenshot;
-                    if (lastScreenshot != null && !lastScreenshot.isRecycled()
-                            && !Objects.equals(lastBitmap, lastScreenshot)) {
-                        updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
+            try {
+                // TODO(b/274427458) check if this can be done in the background.
+                PixelCopy.request(surface, screenShot, (res) -> {
+                    Trace.endAsyncSection(pixelCopySectionName, pixelCopyCount);
+                    if (DEBUG) {
+                        Log.d(TAG, "result of pixel copy is: "
+                                + (res == PixelCopy.SUCCESS ? "SUCCESS" : "FAILURE"));
                     }
-                } else {
-                    mLastScreenshot = finalScreenShot;
-                    // going to hold this lock for a while
-                    currentPage.setBitmap(finalScreenShot);
-                    currentPage.setLastUpdateTime(current);
-                    updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
-                }
-            }, mHandler);
-
+                    if (res != PixelCopy.SUCCESS) {
+                        Bitmap lastBitmap = currentPage.getBitmap();
+                        // assign the last bitmap taken for now
+                        currentPage.setBitmap(mLastScreenshot);
+                        Bitmap lastScreenshot = mLastScreenshot;
+                        if (lastScreenshot != null && !Objects.equals(lastBitmap, lastScreenshot)) {
+                            updatePageColors(
+                                    currentPage, areas, pageIndx, numPages, wallpaperDimAmount);
+                        }
+                    } else {
+                        mLastScreenshot = finalScreenShot;
+                        currentPage.setBitmap(finalScreenShot);
+                        currentPage.setLastUpdateTime(current);
+                        updatePageColors(
+                                currentPage, areas, pageIndx, numPages, wallpaperDimAmount);
+                    }
+                }, mBackgroundHandler);
+            } catch (IllegalArgumentException e) {
+                // this can potentially happen if the surface is invalidated right between the
+                // surface.isValid() check and the PixelCopy operation.
+                // in this case, stop: we'll compute colors on the next processLocalColors call.
+                Log.w(TAG, "Cancelling processLocalColors: exception caught during PixelCopy");
+            }
         }
         // locked by the passed page
-        private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
-                float xOffsetStep) {
+        private void updatePageColors(EngineWindowPage page, Set<RectF> areas,
+                int pageIndx, int numPages, float wallpaperDimAmount) {
             if (page.getBitmap() == null) return;
+            if (!mBackgroundHandler.getLooper().isCurrentThread()) {
+                throw new IllegalStateException(
+                        "ProcessLocalColors should be called from the background thread");
+            }
             Trace.beginSection("WallpaperService#updatePageColors");
             if (DEBUG) {
                 Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
                         + page.getAreas().size() + " and bitmap size of "
                         + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
             }
-            for (RectF area: page.getAreas()) {
+            for (RectF area: areas) {
                 if (area == null) continue;
                 RectF subArea = generateSubRect(area, pageIndx, numPages);
                 Bitmap b = page.getBitmap();
@@ -1769,12 +1820,12 @@
                 int height = Math.round(b.getHeight() * subArea.height());
                 Bitmap target;
                 try {
-                    target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
+                    target = Bitmap.createBitmap(b, x, y, width, height);
                 } catch (Exception e) {
                     Log.e(TAG, "Error creating page local color bitmap", e);
                     continue;
                 }
-                WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount);
+                WallpaperColors color = WallpaperColors.fromBitmap(target, wallpaperDimAmount);
                 target.recycle();
                 WallpaperColors currentColor = page.getColors(area);
 
@@ -1791,12 +1842,14 @@
                                 + " local color callback for area" + area + " for page " + pageIndx
                                 + " of " + numPages);
                     }
-                    try {
-                        mConnection.onLocalWallpaperColorsChanged(area, color,
-                                mDisplayContext.getDisplayId());
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
-                    }
+                    mHandler.post(() -> {
+                        try {
+                            mConnection.onLocalWallpaperColorsChanged(area, color,
+                                    mDisplayContext.getDisplayId());
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+                        }
+                    });
                 }
             }
             Trace.endSection();
@@ -1822,16 +1875,17 @@
             return new RectF(left, in.top, right, in.bottom);
         }
 
+        @GuardedBy("mLock")
         private void resetWindowPages() {
             if (supportsLocalColorExtraction()) return;
             if (!mResetWindowPages) return;
             mResetWindowPages = false;
-            mLastWindowPage = -1;
             for (int i = 0; i < mWindowPages.length; i++) {
                 mWindowPages[i].setLastUpdateTime(0L);
             }
         }
 
+        @GuardedBy("mLock")
         private int getRectFPage(RectF area, float step) {
             if (!isValid(area)) return 0;
             if (!validStep(step)) return 0;
@@ -1852,12 +1906,12 @@
             if (DEBUG) {
                 Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
             }
-            mHandler.post(() -> {
-                mLocalColorsToAdd.addAll(regions);
-                processLocalColors(mPendingXOffset, mPendingYOffset);
+            mBackgroundHandler.post(() -> {
+                synchronized (mLock) {
+                    mLocalColorsToAdd.addAll(regions);
+                }
+                processLocalColors();
             });
-
-
         }
 
         /**
@@ -1867,16 +1921,18 @@
          */
         public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
             if (supportsLocalColorExtraction()) return;
-            mHandler.post(() -> {
-                float step = mPendingXOffsetStep;
-                mLocalColorsToAdd.removeAll(regions);
-                mLocalColorAreas.removeAll(regions);
-                if (!validStep(step)) {
-                    return;
-                }
-                for (int i = 0; i < mWindowPages.length; i++) {
-                    for (int j = 0; j < regions.size(); j++) {
-                        mWindowPages[i].removeArea(regions.get(j));
+            mBackgroundHandler.post(() -> {
+                synchronized (mLock) {
+                    float step = mPendingXOffsetStep;
+                    mLocalColorsToAdd.removeAll(regions);
+                    mLocalColorAreas.removeAll(regions);
+                    if (!validStep(step)) {
+                        return;
+                    }
+                    for (int i = 0; i < mWindowPages.length; i++) {
+                        for (int j = 0; j < regions.size(); j++) {
+                            mWindowPages[i].removeArea(regions.get(j));
+                        }
                     }
                 }
             });
@@ -1894,7 +1950,7 @@
         }
 
         private boolean validStep(float step) {
-            return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
+            return !Float.isNaN(step) && step > 0f && step <= 1f;
         }
 
         void doCommand(WallpaperCommand cmd) {
@@ -2498,6 +2554,9 @@
     @Override
     public void onCreate() {
         Trace.beginSection("WPMS.onCreate");
+        mBackgroundThread = new HandlerThread("DefaultWallpaperLocalColorExtractor");
+        mBackgroundThread.start();
+        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
         super.onCreate();
         Trace.endSection();
     }
@@ -2510,6 +2569,7 @@
             mActiveEngines.get(i).detach();
         }
         mActiveEngines.clear();
+        mBackgroundThread.quitSafely();
         Trace.endSection();
     }
 
diff --git a/core/java/com/android/internal/app/procstats/UidState.java b/core/java/com/android/internal/app/procstats/UidState.java
index 8761b74..4911346 100644
--- a/core/java/com/android/internal/app/procstats/UidState.java
+++ b/core/java/com/android/internal/app/procstats/UidState.java
@@ -150,6 +150,7 @@
     public void resetSafely(long now) {
         mDurations.resetTable();
         mStartTime = now;
+        mProcesses.removeIf(p -> !p.isInUse());
     }
 
     /**
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e7217de..475f7fd 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.jank;
 
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR;
+
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
@@ -94,6 +98,7 @@
 import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
@@ -436,18 +441,37 @@
         mWorker = worker;
         mWorker.start();
         mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
-
-        // Post initialization to the background in case we're running on the main
-        // thread.
-        mWorker.getThreadHandler().post(
-                () -> mPropertiesChangedListener.onPropertiesChanged(
-                        DeviceConfig.getProperties(
-                                DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
-        DeviceConfig.addOnPropertiesChangedListener(
-                DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
-                new HandlerExecutor(mWorker.getThreadHandler()),
-                mPropertiesChangedListener);
         mEnabled = DEFAULT_ENABLED;
+
+        final Context context = ActivityThread.currentApplication();
+        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+            if (DEBUG) {
+                Log.d(TAG, "Initialized the InteractionJankMonitor."
+                        + " (No READ_DEVICE_CONFIG permission to change configs)"
+                        + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+                        + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+                        + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+                        + ", package=" + context.getPackageName());
+            }
+            return;
+        }
+
+        // Post initialization to the background in case we're running on the main thread.
+        mWorker.getThreadHandler().post(
+                () -> {
+                    try {
+                        mPropertiesChangedListener.onPropertiesChanged(
+                                DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
+                        DeviceConfig.addOnPropertiesChangedListener(
+                                NAMESPACE_INTERACTION_JANK_MONITOR,
+                                new HandlerExecutor(mWorker.getThreadHandler()),
+                                mPropertiesChangedListener);
+                    } catch (SecurityException ex) {
+                        Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+                                + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+                                + ", package=" + context.getPackageName());
+                    }
+                });
     }
 
     /**
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6fed26c..e1e57de 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -166,8 +166,8 @@
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
-    // Current on-disk Parcel version
-    static final int VERSION = 210;
+    // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
+    public static final int VERSION = 211;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -6491,6 +6491,9 @@
             addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mPhoneOn = true;
             mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs);
+            if (mConstants.PHONE_ON_EXTERNAL_STATS_COLLECTION) {
+                scheduleSyncExternalStatsLocked("phone-on", ExternalStatsSync.UPDATE_RADIO);
+            }
         }
     }
 
@@ -6509,6 +6512,7 @@
             addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mPhoneOn = false;
             mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs);
+            scheduleSyncExternalStatsLocked("phone-off", ExternalStatsSync.UPDATE_RADIO);
         }
     }
 
@@ -8475,6 +8479,12 @@
 
     @GuardedBy("this")
     @Override
+    public long getPhoneEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_PHONE);
+    }
+
+    @GuardedBy("this")
+    @Override
     public long getScreenOnMeasuredBatteryConsumptionUC() {
         return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
     }
@@ -13717,18 +13727,36 @@
         }
 
         synchronized (this) {
+            final long totalRadioDurationMs =
+                    mMobileRadioActiveTimer.getTimeSinceMarkLocked(
+                            elapsedRealtimeMs * 1000) / 1000;
+            mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
+            final long phoneOnDurationMs = Math.min(totalRadioDurationMs,
+                    mPhoneOnTimer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000);
+            mPhoneOnTimer.setMark(elapsedRealtimeMs);
+
             if (!mOnBatteryInternal || mIgnoreNextExternalStats) {
                 return;
             }
 
             final SparseDoubleArray uidEstimatedConsumptionMah;
+            final long dataConsumedChargeUC;
             if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null
                     && mGlobalMeasuredEnergyStats != null) {
+                // Crudely attribute power consumption. Added (totalRadioDurationMs / 2) to the
+                // numerator for long rounding.
+                final long phoneConsumedChargeUC =
+                        (consumedChargeUC * phoneOnDurationMs + totalRadioDurationMs / 2)
+                                / totalRadioDurationMs;
+                dataConsumedChargeUC = consumedChargeUC - phoneConsumedChargeUC;
                 mGlobalMeasuredEnergyStats.updateStandardBucket(
-                        MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC);
+                        MeasuredEnergyStats.POWER_BUCKET_PHONE, phoneConsumedChargeUC);
+                mGlobalMeasuredEnergyStats.updateStandardBucket(
+                        MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, dataConsumedChargeUC);
                 uidEstimatedConsumptionMah = new SparseDoubleArray();
             } else {
                 uidEstimatedConsumptionMah = null;
+                dataConsumedChargeUC = POWER_DATA_UNAVAILABLE;
             }
 
             if (deltaInfo != null) {
@@ -13888,14 +13916,9 @@
                 // Update the MeasuredEnergyStats information.
                 if (uidEstimatedConsumptionMah != null) {
                     double totalEstimatedConsumptionMah = 0.0;
-
-                    // Estimate total active radio power consumption since last mark.
-                    final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked(
-                            elapsedRealtimeMs * 1000) / 1000;
-                    mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
                     totalEstimatedConsumptionMah +=
                             mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
-                                    totalRadioTimeMs);
+                                    totalRadioDurationMs);
 
                     // Estimate idle power consumption at each signal strength level
                     final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length;
@@ -13919,7 +13942,7 @@
                             mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
 
                     distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
-                            consumedChargeUC, uidEstimatedConsumptionMah,
+                            dataConsumedChargeUC, uidEstimatedConsumptionMah,
                             totalEstimatedConsumptionMah, elapsedRealtimeMs);
                 }
 
@@ -16651,6 +16674,8 @@
         public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
         public static final String KEY_BATTERY_CHARGED_DELAY_MS =
                 "battery_charged_delay_ms";
+        public static final String KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION =
+                "phone_on_external_stats_collection";
 
         private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
         private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
@@ -16663,6 +16688,7 @@
         private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
         private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
         private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
+        private static final boolean DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION = true;
 
         public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
         /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
@@ -16678,6 +16704,8 @@
         public int MAX_HISTORY_FILES;
         public int MAX_HISTORY_BUFFER; /*Bytes*/
         public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
+        public boolean PHONE_ON_EXTERNAL_STATS_COLLECTION =
+                DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION;
 
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -16754,6 +16782,11 @@
                                 DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
                                 : DEFAULT_MAX_HISTORY_BUFFER_KB)
                         * 1024;
+
+                PHONE_ON_EXTERNAL_STATS_COLLECTION = mParser.getBoolean(
+                        KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION,
+                        DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION);
+
                 updateBatteryChargedDelayMsLocked();
             }
         }
@@ -16808,6 +16841,8 @@
             pw.println(MAX_HISTORY_BUFFER/1024);
             pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("=");
             pw.println(BATTERY_CHARGED_DELAY_MS);
+            pw.print(KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION); pw.print("=");
+            pw.println(PHONE_ON_EXTERNAL_STATS_COLLECTION);
         }
     }
 
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index cb893de..f1c4ffe 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -40,14 +40,27 @@
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final long energyConsumerUC = batteryStats.getPhoneEnergyConsumptionUC();
+        final int powerModel = getPowerModel(energyConsumerUC, query);
+
         final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
-        if (phoneOnPower != 0) {
-            builder.getAggregateBatteryConsumerBuilder(
-                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower)
-                    .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs);
+        final double phoneOnPower;
+        switch (powerModel) {
+            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+                phoneOnPower = uCtoMah(energyConsumerUC);
+                break;
+            case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
+            default:
+                phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
         }
+
+        if (phoneOnPower == 0.0)  return;
+
+        builder.getAggregateBatteryConsumerBuilder(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower, powerModel)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs);
+
     }
 }
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index 7fb8696..5bfdd62 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -59,7 +59,9 @@
     public static final int POWER_BUCKET_BLUETOOTH = 5;
     public static final int POWER_BUCKET_GNSS = 6;
     public static final int POWER_BUCKET_MOBILE_RADIO = 7;
-    public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom.
+    public static final int POWER_BUCKET_CAMERA = 8;
+    public static final int POWER_BUCKET_PHONE = 9;
+    public static final int NUMBER_STANDARD_POWER_BUCKETS = 10;  // Buckets above this are custom.
 
     @IntDef(prefix = {"POWER_BUCKET_"}, value = {
             POWER_BUCKET_UNKNOWN,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 33f9219d..d706ea8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5333,6 +5333,10 @@
     <!-- Whether vertical reachability repositioning is allowed for letterboxed fullscreen apps. -->
     <bool name="config_letterboxIsVerticalReachabilityEnabled">false</bool>
 
+    <!-- Whether book mode automatic horizontal reachability positioning is allowed for letterboxed
+        fullscreen apps -->
+    <bool name="config_letterboxIsAutomaticReachabilityInBookModeEnabled">false</bool>
+
     <!-- Default horizontal position of the letterboxed app window when reachability is
         enabled and an app is fullscreen in landscape device orientation. When reachability is
         enabled, the position can change between left, center and right. This config defines the
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a0d69e2..1904cbf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4475,6 +4475,7 @@
   <java-symbol type="dimen" name="config_letterboxTabletopModePositionMultiplier" />
   <java-symbol type="bool" name="config_letterboxIsHorizontalReachabilityEnabled" />
   <java-symbol type="bool" name="config_letterboxIsVerticalReachabilityEnabled" />
+  <java-symbol type="bool" name="config_letterboxIsAutomaticReachabilityInBookModeEnabled" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForHorizontalReachability" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForBookModeReachability" />
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
index d4276ef..6189914 100644
--- a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -186,4 +186,19 @@
                         eq(0),
                         eq(APP_1_PROCESS_NAME));
     }
+
+    @SmallTest
+    public void testSafelyResetClearsProcessInUidState() throws Exception {
+        ProcessStats processStats = new ProcessStats();
+        ProcessState processState =
+                processStats.getProcessStateLocked(
+                        APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+        processState.makeActive();
+        UidState uidState = processStats.mUidStates.get(APP_1_UID);
+        assertTrue(uidState.isInUse());
+        processState.makeInactive();
+        uidState.resetSafely(NOW_MS);
+        processState.makeActive();
+        assertFalse(uidState.isInUse());
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 00ac198..0bdf491 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -245,6 +245,8 @@
         stats.noteNetworkInterfaceForTransports("cellular",
                 new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
+        stats.notePhoneOnLocked(9800, 9800);
+
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
@@ -257,27 +259,33 @@
 
         mStatsRule.setTime(12_000, 12_000);
 
-        MobileRadioPowerCalculator calculator =
+        MobileRadioPowerCalculator mobileRadioPowerCalculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
-
-        mStatsRule.apply(calculator);
+        PhonePowerCalculator phonePowerCalculator =
+                new PhonePowerCalculator(mStatsRule.getPowerProfile());
+        mStatsRule.apply(mobileRadioPowerCalculator, phonePowerCalculator);
 
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(1.53934);
+                .isWithin(PRECISION).of(1.38541);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(2.77778);
+                .isWithin(PRECISION).of(2.5);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
 
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
+                .isWithin(PRECISION).of(0.27778);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(1.53934);
+                .isWithin(PRECISION).of(1.38541);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
diff --git a/libs/WindowManager/Shell/res/color/unfold_background.xml b/libs/WindowManager/Shell/res/color/unfold_background.xml
new file mode 100644
index 0000000..e33eb12
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/unfold_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_500" android:lStar="5" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index cb1a6e7..ac6e4c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -59,7 +59,7 @@
      */
     private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
             SystemProperties.getBoolean(
-                    "persist.wm.debug.enable_move_floating_window_in_tabletop", false);
+                    "persist.wm.debug.enable_move_floating_window_in_tabletop", true);
 
     /**
      * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index ffc56b6..b4acd60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -727,6 +727,10 @@
         getRefBounds2(mTempRect);
         t.setPosition(leash2, mTempRect.left, mTempRect.top)
                 .setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
+        // Make right or bottom side surface always higher than left or top side to avoid weird
+        // animation when dismiss split. e.g. App surface fling above on decor surface.
+        t.setLayer(leash1, 1);
+        t.setLayer(leash2, 2);
 
         if (mImePositionProcessor.adjustSurfaceLayoutForIme(
                 t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6831a47..1dd2ef9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -538,6 +539,15 @@
             mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
             return;
         }
+        if (mSplitScreenOptional.isPresent()) {
+            // If pip activity will reparent to origin task case and if the origin task still under
+            // split root, just exit split screen here to ensure it could expand to fullscreen.
+            SplitScreenController split = mSplitScreenOptional.get();
+            if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+                split.exitSplitScreen(INVALID_TASK_ID,
+                        SplitScreenController.EXIT_REASON_APP_FINISHED);
+            }
+        }
         mSyncTransactionQueue.queue(wct);
         mSyncTransactionQueue.runInSync(t -> {
             // Make sure to grab the latest source hint rect as it could have been
@@ -1481,9 +1491,13 @@
                 applyFinishBoundsResize(wct, direction, false);
             }
         } else {
-            final boolean isPipTopLeft =
-                    direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft();
-            applyFinishBoundsResize(wct, direction, isPipTopLeft);
+            applyFinishBoundsResize(wct, direction, isPipToTopLeft());
+            // Use sync transaction to apply finish transaction for enter split case.
+            if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+                mSyncTransactionQueue.runInSync(t -> {
+                    t.merge(tx);
+                });
+            }
         }
 
         finishResizeForMenu(destinationBounds);
@@ -1520,7 +1534,10 @@
         mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
 
         wct.setBounds(mToken, taskBounds);
-        wct.setBoundsChangeTransaction(mToken, tx);
+        // Pip to split should use sync transaction to sync split bounds change.
+        if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+            wct.setBoundsChangeTransaction(mToken, tx);
+        }
     }
 
     /**
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 b59f95c..9807320 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
@@ -161,6 +161,10 @@
             this::onKeepClearAreasChangedCallback;
 
     private void onKeepClearAreasChangedCallback() {
+        if (mIsKeyguardShowingOrAnimating) {
+            // early bail out if the change was caused by keyguard showing up
+            return;
+        }
         if (!mEnablePipKeepClearAlgorithm) {
             // early bail out if the keep clear areas feature is disabled
             return;
@@ -186,6 +190,10 @@
             // early bail out if the keep clear areas feature is disabled
             return;
         }
+        if (mIsKeyguardShowingOrAnimating) {
+            // early bail out if the change was caused by keyguard showing up
+            return;
+        }
         // only move if we're in PiP or transitioning into PiP
         if (!mPipTransitionState.shouldBlockResizeRequest()) {
             Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
@@ -635,9 +643,11 @@
                         DisplayLayout pendingLayout =
                                 mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
                         if (mIsInFixedRotation
+                                || mIsKeyguardShowingOrAnimating
                                 || pendingLayout.rotation()
                                 != mPipBoundsState.getDisplayLayout().rotation()) {
-                            // bail out if there is a pending rotation or fixed rotation change
+                            // bail out if there is a pending rotation or fixed rotation change or
+                            // there's a keyguard present
                             return;
                         }
                         int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
@@ -931,10 +941,10 @@
                     mPipBoundsState.getDisplayBounds().right,
                     mPipBoundsState.getDisplayBounds().bottom);
             mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
-            updatePipPositionForKeepClearAreas();
         } else {
             mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
         }
+        updatePipPositionForKeepClearAreas();
     }
 
     private void setLauncherAppIconSize(int iconSizePx) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 979b7c7..167c032 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -282,7 +282,8 @@
 
     public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
         final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
-                && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
+                && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground(
+                taskInfo.taskId);
         mFocusedTaskAllowSplitScreen = isSplitScreen
                 || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                 && taskInfo.supportsMultiWindow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index d03d075..ff5138d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -212,24 +212,25 @@
          */
         @Override
         public Size getSizeForAspectRatio(Size size, float aspectRatio) {
-            // getting the percentage of the max size that current size takes
             float currAspectRatio = (float) size.getWidth() / size.getHeight();
+
+            // getting the percentage of the max size that current size takes
             Size currentMaxSize = getMaxSize(currAspectRatio);
             float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
 
             // getting the max size for the target aspect ratio
             Size updatedMaxSize = getMaxSize(aspectRatio);
 
-            int width = (int) (updatedMaxSize.getWidth() * currentPercent);
-            int height = (int) (updatedMaxSize.getHeight() * currentPercent);
+            int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
+            int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
 
             // adjust the dimensions if below allowed min edge size
             if (width < getMinEdgeSize() && aspectRatio <= 1) {
                 width = getMinEdgeSize();
-                height = (int) (width / aspectRatio);
+                height = Math.round(width / aspectRatio);
             } else if (height < getMinEdgeSize() && aspectRatio > 1) {
                 height = getMinEdgeSize();
-                width = (int) (height * aspectRatio);
+                width = Math.round(height * aspectRatio);
             }
 
             // reduce the dimensions of the updated size to the calculated percentage
@@ -365,7 +366,7 @@
         mContext = context;
 
         boolean enablePipSizeLargeScreen = SystemProperties
-                .getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
+                .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true);
 
         // choose between two implementations of size spec logic
         if (enablePipSizeLargeScreen) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 94b9e90..7d5ab84 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,7 +31,6 @@
 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -89,7 +88,6 @@
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -329,9 +327,14 @@
         return mTaskOrganizer.getRunningTaskInfo(taskId);
     }
 
+    /** Check task is under split or not by taskId. */
     public boolean isTaskInSplitScreen(int taskId) {
-        return isSplitScreenVisible()
-                && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+        return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+    }
+
+    /** Check split is foreground and task is under split or not by taskId. */
+    public boolean isTaskInSplitScreenForeground(int taskId) {
+        return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
     }
 
     public @SplitPosition int getSplitPosition(int taskId) {
@@ -339,8 +342,7 @@
     }
 
     public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
-        return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
-                new WindowContainerTransaction());
+        return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction());
     }
 
     /**
@@ -351,13 +353,13 @@
         mStageCoordinator.updateSurfaces(transaction);
     }
 
-    private boolean moveToStage(int taskId, @StageType int stageType,
-            @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+    private boolean moveToStage(int taskId, @SplitPosition int stagePosition,
+            WindowContainerTransaction wct) {
         final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
         if (task == null) {
             throw new IllegalArgumentException("Unknown taskId" + taskId);
         }
-        return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct);
+        return mStageCoordinator.moveToStage(task, stagePosition, wct);
     }
 
     public boolean removeFromSideStage(int taskId) {
@@ -382,10 +384,9 @@
     }
 
     public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
-        final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
         final int stagePosition =
                 leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
-        moveToStage(taskId, stageType, stagePosition, wct);
+        moveToStage(taskId, stagePosition, wct);
     }
 
     public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 71ee690..f159f54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -399,56 +399,43 @@
         return STAGE_TYPE_UNDEFINED;
     }
 
-    boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType,
-            @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+    boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
+            WindowContainerTransaction wct) {
         StageTaskListener targetStage;
         int sideStagePosition;
-        if (stageType == STAGE_TYPE_MAIN) {
-            targetStage = mMainStage;
-            sideStagePosition = reverseSplitPosition(stagePosition);
-        } else if (stageType == STAGE_TYPE_SIDE) {
+        if (isSplitScreenVisible()) {
+            // If the split screen is foreground, retrieves target stage based on position.
+            targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
+            sideStagePosition = mSideStagePosition;
+        } else {
             targetStage = mSideStage;
             sideStagePosition = stagePosition;
-        } else {
-            if (isSplitScreenVisible()) {
-                // If the split screen is activated, retrieves target stage based on position.
-                targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
-                sideStagePosition = mSideStagePosition;
-            } else {
-                // Exit split if it running background.
-                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
-
-                targetStage = mSideStage;
-                sideStagePosition = stagePosition;
-            }
         }
 
         if (!isSplitActive()) {
-            // prevent the fling divider to center transitioni if split screen didn't active.
-            mIsDropEntering = true;
+            mSplitLayout.init();
+            prepareEnterSplitScreen(wct, task, stagePosition);
+            mSyncQueue.queue(wct);
+            mSyncQueue.runInSync(t -> {
+                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+            });
+        } else {
+            setSideStagePosition(sideStagePosition, wct);
+            targetStage.addTask(task, wct);
+            targetStage.evictAllChildren(wct);
+            if (!isSplitScreenVisible()) {
+                final StageTaskListener anotherStage = targetStage == mMainStage
+                        ? mSideStage : mMainStage;
+                anotherStage.reparentTopTask(wct);
+                anotherStage.evictAllChildren(wct);
+                wct.reorder(mRootTaskInfo.token, true);
+            }
+            setRootForceTranslucent(false, wct);
+            mSyncQueue.queue(wct);
         }
 
-        setSideStagePosition(sideStagePosition, wct);
-        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        targetStage.evictAllChildren(evictWct);
-
-        // Apply surface bounds before animation start.
-        SurfaceControl.Transaction startT = mTransactionPool.acquire();
-        if (startT != null) {
-            updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
-            startT.apply();
-            mTransactionPool.release(startT);
-        }
-        // reparent the task to an invisible split root will make the activity invisible.  Reorder
-        // the root task to front to make the entering transition from pip to split smooth.
-        wct.reorder(mRootTaskInfo.token, true);
-        wct.reorder(targetStage.mRootTaskInfo.token, true);
-        targetStage.addTask(task, wct);
-
-        if (!evictWct.isEmpty()) {
-            wct.merge(evictWct, true /* transfer */);
-        }
-        mTaskOrganizer.applyTransaction(wct);
+        // Due to drag already pip task entering split by this method so need to reset flag here.
+        mIsDropEntering = false;
         return true;
     }
 
@@ -1367,7 +1354,7 @@
                     mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
                     mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
                     finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
-                    setRootForceTranslucent(true, wct);
+                    setRootForceTranslucent(true, finishedWCT);
                     finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
                     mSyncQueue.queue(finishedWCT);
                     mSyncQueue.runInSync(at -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a841b7f..d6f4d6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -220,12 +220,20 @@
                 mCallbacks.onNoLongerSupportMultiWindow();
                 return;
             }
-            mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+            if (taskInfo.topActivity == null && mChildrenTaskInfo.contains(taskInfo.taskId)
+                    && mChildrenTaskInfo.get(taskInfo.taskId).topActivity != null) {
+                // If top activity become null, it means the task is about to vanish, we use this
+                // signal to remove it from children list earlier for smooth dismiss transition.
+                mChildrenTaskInfo.remove(taskInfo.taskId);
+                mChildrenLeashes.remove(taskInfo.taskId);
+            } else {
+                mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+            }
             mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
                     taskInfo.isVisible);
-            if (!ENABLE_SHELL_TRANSITIONS) {
-                updateChildTaskSurface(
-                        taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+            if (!ENABLE_SHELL_TRANSITIONS && mChildrenLeashes.contains(taskInfo.taskId)) {
+                updateChildTaskSurface(taskInfo, mChildrenLeashes.get(taskInfo.taskId),
+                        false /* firstAppeared */);
             }
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
index 86ca292..fe0a3fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -79,7 +79,7 @@
     }
 
     private float[] getBackgroundColor(Context context) {
-        int colorInt = context.getResources().getColor(R.color.taskbar_background);
+        int colorInt = context.getResources().getColor(R.color.unfold_background);
         return new float[]{
                 (float) red(colorInt) / 255.0F,
                 (float) green(colorInt) / 255.0F,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 0bb809d..0b528ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -144,39 +144,48 @@
     }
 
     @Test
-    public void testMoveToStage() {
+    public void testMoveToStage_splitActiveBackground() {
+        when(mStageCoordinator.isSplitActive()).thenReturn(true);
+
         final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                new WindowContainerTransaction());
-        verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
-        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
-
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                new WindowContainerTransaction());
-        verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+        verify(mSideStage).addTask(eq(task), eq(wct));
         assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
     }
 
     @Test
-    public void testMoveToUndefinedStage() {
-        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
-
-        // Verify move to undefined stage while split screen not activated moves task to side stage.
-        when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false);
-        mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                new WindowContainerTransaction());
-        verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
-        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
-
-        // Verify move to undefined stage after split screen activated moves task based on position.
+    public void testMoveToStage_splitActiveForeground() {
+        when(mStageCoordinator.isSplitActive()).thenReturn(true);
         when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
-        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
-                new WindowContainerTransaction());
-        verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
-        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
+        // Assume current side stage is top or left.
+        mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+        verify(mMainStage).addTask(eq(task), eq(wct));
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct);
+        verify(mSideStage).addTask(eq(task), eq(wct));
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+    }
+
+    @Test
+    public void testMoveToStage_splitInctive() {
+        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+        verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+                eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
     }
 
     @Test
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index a285462..d101a1b 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -39,6 +39,9 @@
 #include "VectorDrawable.h"
 #include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/FunctorDrawable.h"
+#ifdef __ANDROID__
+#include "renderthread/CanvasContext.h"
+#endif
 
 namespace android {
 namespace uirenderer {
@@ -434,7 +437,19 @@
     size_t count;
     SkPaint paint;
     void draw(SkCanvas* c, const SkMatrix&) const {
-        c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+        if (paint.isAntiAlias()) {
+            c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+        } else {
+            c->save();
+#ifdef __ANDROID__
+            auto pixelSnap = renderthread::CanvasContext::getActiveContext()->getPixelSnapMatrix();
+            auto transform = c->getLocalToDevice();
+            transform.postConcat(pixelSnap);
+            c->setMatrix(transform);
+#endif
+            c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+            c->restore();
+        }
     }
 };
 struct DrawVertices final : Op {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index a80c613..fe41420 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -53,6 +53,14 @@
     bool isSurfaceReady() override;
     bool isContextReady() override;
 
+    const SkM44& getPixelSnapMatrix() const override {
+        // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+        // desired fragment
+        static const SkScalar kOffset = 0.063f;
+        static const SkM44 sSnapMatrix = SkM44::Translate(kOffset, kOffset);
+        return sSnapMatrix;
+    }
+
     static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
 
 protected:
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index cc2565d..18e0b91 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -180,6 +180,10 @@
     }
 }
 
+const SkM44& SkiaVulkanPipeline::getPixelSnapMatrix() const {
+    return mVkSurface->getPixelSnapMatrix();
+}
+
 } /* namespace skiapipeline */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index a6e685d..7c8f65b 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -49,6 +49,7 @@
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
+    const SkM44& getPixelSnapMatrix() const override;
 
     static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
     static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 602554a..f56d19b 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -793,6 +793,10 @@
     return size;
 }
 
+const SkM44& CanvasContext::getPixelSnapMatrix() const {
+    return mRenderPipeline->getPixelSnapMatrix();
+}
+
 void CanvasContext::prepareAndDraw(RenderNode* node) {
     ATRACE_CALL();
 
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 951ee21..d85e579 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -195,6 +195,9 @@
 
     SkISize getNextFrameSize() const;
 
+    // Returns the matrix to use to nudge non-AA'd points/lines towards the fragment center
+    const SkM44& getPixelSnapMatrix() const;
+
     // Called when SurfaceStats are available.
     static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
                                         ASurfaceControlStats* stats);
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index ef58bc5..54adec2 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -88,6 +88,8 @@
     virtual void setPictureCapturedCallback(
             const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
 
+    virtual const SkM44& getPixelSnapMatrix() const = 0;
+
     virtual ~IRenderPipeline() {}
 };
 
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 7dd3561..666f329 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -63,6 +63,18 @@
     return SkMatrix::I();
 }
 
+static SkM44 GetPixelSnapMatrix(SkISize windowSize, int transform) {
+    // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+    // desired fragment
+    static const SkScalar kOffset = 0.063f;
+    SkMatrix preRotation = GetPreTransformMatrix(windowSize, transform);
+    SkMatrix invert;
+    LOG_ALWAYS_FATAL_IF(!preRotation.invert(&invert));
+    return SkM44::Translate(kOffset, kOffset)
+            .postConcat(SkM44(preRotation))
+            .preConcat(SkM44(invert));
+}
+
 static bool ConnectAndSetWindowDefaults(ANativeWindow* window) {
     ATRACE_CALL();
 
@@ -178,6 +190,8 @@
 
     outWindowInfo->preTransform =
             GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform);
+    outWindowInfo->pixelSnapMatrix =
+            GetPixelSnapMatrix(outWindowInfo->size, outWindowInfo->transform);
 
     err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value);
     if (err != 0 || query_value < 0) {
@@ -406,6 +420,7 @@
         }
 
         mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform);
+        mWindowInfo.pixelSnapMatrix = GetPixelSnapMatrix(mWindowInfo.size, mWindowInfo.transform);
     }
 
     uint32_t idx;
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index beb71b72..b8ccf78 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -45,6 +45,8 @@
     }
     const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; }
 
+    const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; }
+
 private:
     /*
      * All structs/methods in this private section are specifically for use by the VulkanManager
@@ -101,6 +103,7 @@
         SkISize actualSize;
         // transform to be applied to the SkSurface to map the coordinates to the provided transform
         SkMatrix preTransform;
+        SkM44 pixelSnapMatrix;
     };
 
     VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrDirectContext* grContext);
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index a317178..762dcdc 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -55,7 +55,7 @@
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:textSize="@dimen/chipbar_text_size"
-            android:textColor="@android:color/system_accent2_900"
+            android:textColor="@color/chipbar_text_and_icon_color"
             android:alpha="0.0"
             />
 
diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
index b374074..80f5d87 100644
--- a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
+++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
@@ -24,7 +24,7 @@
     android:orientation="horizontal"
     android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
     android:background="@drawable/status_bar_user_chip_bg"
-    android:visibility="visible" >
+    android:visibility="gone" >
     <ImageView android:id="@+id/current_user_avatar"
         android:layout_width="@dimen/status_bar_user_chip_avatar_size"
         android:layout_height="@dimen/status_bar_user_chip_avatar_size"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3763b17..9d00a27 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -230,6 +230,9 @@
 
     <color name="people_tile_background">@color/material_dynamic_secondary95</color>
 
+    <!-- Chipbar -->
+    <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color>
+
     <!-- Internet Dialog -->
     <!-- Material next state on color-->
     <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 4aaa566..3b9060a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -259,7 +259,7 @@
                 largeTimeListener?.update(shouldTimeListenerRun)
             }
 
-            override fun onTimeFormatChanged(timeFormat: String) {
+            override fun onTimeFormatChanged(timeFormat: String?) {
                 clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
             }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index b85b2b8..ba21780 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,6 +22,7 @@
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 
+import android.annotation.Nullable;
 import android.database.ContentObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -458,6 +459,7 @@
         mView.setClock(clock, mStatusBarStateController.getState());
     }
 
+    @Nullable
     private ClockController getClock() {
         return mClockEventController.getClock();
     }
@@ -510,8 +512,10 @@
     }
 
     /** Gets the animations for the current clock. */
+    @Nullable
     public ClockAnimations getClockAnimations() {
-        return getClock().getAnimations();
+        ClockController clock = getClock();
+        return clock == null ? null : clock.getAnimations();
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index c098d4c..061bab8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -260,6 +260,14 @@
          */
         @Override
         public void finish(boolean strongAuth, int targetUserId) {
+            if (mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)
+                    && !mKeyguardStateController.canDismissLockScreen() && !strongAuth) {
+                Log.e(TAG,
+                        "Tried to dismiss keyguard when lockscreen is not dismissible and user "
+                                + "was not authenticated with a primary security method "
+                                + "(pin/password/pattern).");
+                return;
+            }
             // If there's a pending runnable because the user interacted with a widget
             // and we're leaving keyguard, then run it.
             boolean deferKeyguardDone = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1ccde32..e154695 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -68,6 +68,7 @@
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
 import android.annotation.AnyThread;
@@ -1601,7 +1602,7 @@
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT,
                     "assistant",
-                    false);
+                    /* dismissKeyguard */ true);
         }
     }
 
@@ -1862,6 +1863,11 @@
                         updateFaceListeningState(BIOMETRIC_ACTION_STOP,
                                 FACE_AUTH_UPDATED_POSTURE_CHANGED);
                     }
+                    if (mPostureState == DEVICE_POSTURE_OPENED) {
+                        mLogger.d("Posture changed to open - attempting to request active unlock");
+                        requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE,
+                                false);
+                    }
                 }
             };
 
@@ -1988,26 +1994,10 @@
             FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
             updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
                     FACE_AUTH_UPDATED_STARTED_WAKING_UP);
-
-            final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin =
-                    mActiveUnlockConfig.isWakeupConsideredUnlockIntent(pmWakeReason)
-                            ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
-                            : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE;
-            final String reason = "wakingUp - " + PowerManager.wakeReasonToString(pmWakeReason);
-            if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(pmWakeReason)) {
-                requestActiveUnlockDismissKeyguard(
-                        requestOrigin,
-                        reason
-                );
-            } else {
-                requestActiveUnlock(
-                        requestOrigin,
-                        reason
-                );
-            }
         } else {
             mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
         }
+        requestActiveUnlockFromWakeReason(pmWakeReason, true);
 
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2640,6 +2630,32 @@
         }
     }
 
+    private void requestActiveUnlockFromWakeReason(@PowerManager.WakeReason int wakeReason,
+            boolean powerManagerWakeup) {
+        if (!mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(wakeReason)) {
+            mLogger.logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason);
+            return;
+        }
+
+        final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin =
+                mActiveUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)
+                        ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+                        : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE;
+        final String reason = "wakingUp - " + PowerManager.wakeReasonToString(wakeReason)
+                + " powerManagerWakeup=" + powerManagerWakeup;
+        if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) {
+            requestActiveUnlockDismissKeyguard(
+                    requestOrigin,
+                    reason
+            );
+        } else {
+            requestActiveUnlock(
+                    requestOrigin,
+                    reason
+            );
+        }
+    }
+
     /**
      * Attempts to trigger active unlock from trust agent.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 5162807..1661806 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -62,6 +62,16 @@
         )
     }
 
+    fun logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason: Int) {
+        logBuffer.log(
+            "ActiveUnlock",
+            DEBUG,
+            { int1 = wakeReason },
+            { "Skip requesting active unlock from wake reason that doesn't trigger face auth" +
+                    " reason=${PowerManager.wakeReasonToString(int1)}" }
+        )
+    }
+
     fun logAuthInterruptDetected(active: Boolean) {
         logBuffer.log(TAG, DEBUG, { bool1 = active }, { "onAuthInterruptDetected($bool1)" })
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 541f69c..6742ea9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -102,11 +102,6 @@
         }
     }
 
-    override fun onInit() {
-        mView.setAlphaInDuration(sysuiContext.resources.getInteger(
-                R.integer.auth_ripple_alpha_in_duration).toLong())
-    }
-
     @VisibleForTesting
     public override fun onViewAttached() {
         authController.addCallback(authControllerCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 8409462..b007134 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -54,12 +54,11 @@
     private var lockScreenColorVal = Color.WHITE
     private val fadeDuration = 83L
     private val retractDuration = 400L
-    private var alphaInDuration: Long = 0
     private val dwellShader = DwellRippleShader()
     private val dwellPaint = Paint()
     private val rippleShader = RippleShader()
     private val ripplePaint = Paint()
-    private var unlockedRippleAnimator: AnimatorSet? = null
+    private var unlockedRippleAnimator: Animator? = null
     private var fadeDwellAnimator: Animator? = null
     private var retractDwellAnimator: Animator? = null
     private var dwellPulseOutAnimator: Animator? = null
@@ -85,12 +84,12 @@
         }
 
     init {
-        rippleShader.color = 0xffffffff.toInt() // default color
         rippleShader.rawProgress = 0f
         rippleShader.pixelDensity = resources.displayMetrics.density
         rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
         updateRippleFadeParams()
         ripplePaint.shader = rippleShader
+        setLockScreenColor(0xffffffff.toInt()) // default color
 
         dwellShader.color = 0xffffffff.toInt() // default color
         dwellShader.progress = 0f
@@ -111,10 +110,6 @@
         dwellRadius = sensorRadius * 1.5f
     }
 
-    fun setAlphaInDuration(duration: Long) {
-        alphaInDuration = duration
-    }
-
     /**
      * Animate dwell ripple inwards back to radius 0
      */
@@ -253,7 +248,6 @@
 
                 override fun onAnimationEnd(animation: Animator?) {
                     drawDwell = false
-                    resetRippleAlpha()
                 }
             })
             start()
@@ -277,22 +271,7 @@
             }
         }
 
-        val alphaInAnimator = ValueAnimator.ofInt(0, 62).apply {
-            duration = alphaInDuration
-            addUpdateListener { animator ->
-                rippleShader.color = ColorUtils.setAlphaComponent(
-                    rippleShader.color,
-                    animator.animatedValue as Int
-                )
-                invalidate()
-            }
-        }
-
-        unlockedRippleAnimator = AnimatorSet().apply {
-            playTogether(
-                rippleAnimator,
-                alphaInAnimator
-            )
+        unlockedRippleAnimator = rippleAnimator.apply {
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator?) {
                     drawRipple = true
@@ -310,17 +289,12 @@
         unlockedRippleAnimator?.start()
     }
 
-    fun resetRippleAlpha() {
-        rippleShader.color = ColorUtils.setAlphaComponent(
-                rippleShader.color,
-                255
-        )
-    }
-
     fun setLockScreenColor(color: Int) {
         lockScreenColorVal = color
-        rippleShader.color = lockScreenColorVal
-        resetRippleAlpha()
+        rippleShader.color = ColorUtils.setAlphaComponent(
+                lockScreenColorVal,
+                62
+        )
     }
 
     fun updateDwellRippleColor(isDozing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
index 5dabbbb..6a6c3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.common.shared.model
 
-import androidx.annotation.AttrRes
+import androidx.annotation.ColorRes
 
 /** Models an icon with a specific tint. */
 data class TintedIcon(
     val icon: Icon,
-    @AttrRes val tintAttr: Int?,
+    @ColorRes val tint: Int?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
index dea8cfd..bcc5932 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -17,15 +17,14 @@
 package com.android.systemui.common.ui.binder
 
 import android.widget.ImageView
-import com.android.settingslib.Utils
 import com.android.systemui.common.shared.model.TintedIcon
 
 object TintedIconViewBinder {
     /**
      * Binds the given tinted icon to the view.
      *
-     * [TintedIcon.tintAttr] will always be applied, meaning that if it is null, then the tint
-     * *will* be reset to null.
+     * [TintedIcon.tint] will always be applied, meaning that if it is null, then the tint *will* be
+     * reset to null.
      */
     fun bind(
         tintedIcon: TintedIcon,
@@ -33,8 +32,8 @@
     ) {
         IconViewBinder.bind(tintedIcon.icon, view)
         view.imageTintList =
-            if (tintedIcon.tintAttr != null) {
-                Utils.getColorAttr(view.context, tintedIcon.tintAttr)
+            if (tintedIcon.tint != null) {
+                view.resources.getColorStateList(tintedIcon.tint, view.context.theme)
             } else {
                 null
             }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 163990e..d283379 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -608,6 +608,7 @@
         if (items.size == 1) {
             spinner.setBackground(null)
             anchor.setOnClickListener(null)
+            anchor.isClickable = false
             return
         } else {
             spinner.background = parent.context.resources
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index fc3263f..f0aefb5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -398,7 +398,8 @@
         }
         if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
                 || mState == State.DOZE_AOD || mState == State.DOZE
-                || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) {
+                || mState == State.DOZE_AOD_DOCKED || mState == State.DOZE_SUSPEND_TRIGGERS)
+                && requestedState == State.DOZE_PULSE_DONE) {
             Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
             return mState;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
index 055cd52..7f567aa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
@@ -23,6 +23,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback;
 import com.android.systemui.dreams.conditions.DreamCondition;
+import com.android.systemui.flags.RestartDozeListener;
 import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.condition.ConditionalCoreStartable;
 
@@ -39,17 +40,19 @@
     private final Monitor mConditionMonitor;
     private final DreamCondition mDreamCondition;
     private final DreamStatusBarStateCallback mCallback;
+    private RestartDozeListener mRestartDozeListener;
 
 
     @Inject
     public DreamMonitor(Monitor monitor, DreamCondition dreamCondition,
             @Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor,
-            DreamStatusBarStateCallback callback) {
+            DreamStatusBarStateCallback callback,
+            RestartDozeListener restartDozeListener) {
         super(pretextMonitor);
         mConditionMonitor = monitor;
         mDreamCondition = dreamCondition;
         mCallback = callback;
-
+        mRestartDozeListener = restartDozeListener;
     }
 
     @Override
@@ -61,5 +64,8 @@
         mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
                 .addCondition(mDreamCondition)
                 .build());
+
+        mRestartDozeListener.init();
+        mRestartDozeListener.maybeRestartSleep();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
index 2befce7..5bbfbda 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.dreams.conditions;
 
+import android.app.DreamManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +31,7 @@
  */
 public class DreamCondition extends Condition {
     private final Context mContext;
+    private final DreamManager mDreamManager;
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -39,8 +41,10 @@
     };
 
     @Inject
-    public DreamCondition(Context context) {
+    public DreamCondition(Context context,
+            DreamManager dreamManager) {
         mContext = context;
+        mDreamManager = dreamManager;
     }
 
     private void processIntent(Intent intent) {
@@ -62,8 +66,8 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_DREAMING_STARTED);
         filter.addAction(Intent.ACTION_DREAMING_STOPPED);
-        final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter);
-        processIntent(stickyIntent);
+        mContext.registerReceiver(mReceiver, filter);
+        updateCondition(mDreamManager.isDreaming());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 06ca0ad..28c45b8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.InitializationChecker
-import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -38,8 +37,6 @@
     private val featureFlags: FeatureFlagsDebug,
     private val broadcastSender: BroadcastSender,
     private val initializationChecker: InitializationChecker,
-    private val restartDozeListener: RestartDozeListener,
-    private val delayableExecutor: DelayableExecutor
 ) : CoreStartable {
 
     init {
@@ -55,9 +52,6 @@
             // protected broadcast should only be sent for the main process
             val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
             broadcastSender.sendBroadcast(intent)
-
-            restartDozeListener.init()
-            delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
index 133e67f..f97112d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -18,8 +18,6 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.InitializationChecker
-import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -31,9 +29,6 @@
 constructor(
     dumpManager: DumpManager,
     featureFlags: FeatureFlags,
-    private val initializationChecker: InitializationChecker,
-    private val restartDozeListener: RestartDozeListener,
-    private val delayableExecutor: DelayableExecutor
 ) : CoreStartable {
 
     init {
@@ -42,12 +37,7 @@
         }
     }
 
-    override fun start() {
-        if (initializationChecker.initializeComponents()) {
-            restartDozeListener.init()
-            delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000)
-        }
-    }
+    override fun start() {}
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 30e9916..2bfd9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -233,7 +233,12 @@
     /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/272091103): Tracking Bug
     @JvmField
-    val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true)
+    val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = false)
+
+    /** Whether to inflate the bouncer view on a background thread. */
+    // TODO(b/273341787): Tracking Bug
+    @JvmField
+    val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard")
 
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
@@ -253,20 +258,12 @@
     // TODO(b/270223352): Tracking Bug
     @JvmField
     val HIDE_SMARTSPACE_ON_DREAM_OVERLAY =
-        unreleasedFlag(
-            404,
-            "hide_smartspace_on_dream_overlay",
-            teamfood = true
-    )
+        releasedFlag(404, "hide_smartspace_on_dream_overlay")
 
     // TODO(b/271460958): Tracking Bug
     @JvmField
     val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY =
-        unreleasedFlag(
-            405,
-            "show_weather_complication_on_dream_overlay",
-            teamfood = true
-        )
+        releasedFlag(405, "show_weather_complication_on_dream_overlay")
 
     // 500 - quick settings
 
@@ -420,7 +417,7 @@
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
     // TODO(b/270882464): Tracking Bug
-    val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true)
+    val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
 
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
@@ -522,7 +519,7 @@
     @JvmField
     val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
         sysPropBooleanFlag(
-            1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = false)
+            1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true)
 
     // 1200 - predictive back
     @Keep
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
index bd74f4e..dc0de2c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
@@ -20,7 +20,9 @@
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -33,18 +35,19 @@
     private val statusBarStateController: StatusBarStateController,
     private val powerManager: PowerManager,
     private val systemClock: SystemClock,
+    @Background val bgExecutor: DelayableExecutor,
 ) {
 
     companion object {
-        @VisibleForTesting val RESTART_NAP_KEY = "restart_nap_after_start"
+        @VisibleForTesting val RESTART_SLEEP_KEY = "restart_nap_after_start"
     }
 
     private var inited = false
 
     val listener =
         object : StatusBarStateController.StateListener {
-            override fun onDreamingChanged(isDreaming: Boolean) {
-                settings.putBool(RESTART_NAP_KEY, isDreaming)
+            override fun onDozingChanged(isDozing: Boolean) {
+                storeSleepState(isDozing)
             }
         }
 
@@ -62,11 +65,23 @@
     }
 
     fun maybeRestartSleep() {
-        if (settings.getBool(RESTART_NAP_KEY, false)) {
-            Log.d("RestartDozeListener", "Restarting sleep state")
-            powerManager.wakeUp(systemClock.uptimeMillis())
-            powerManager.goToSleep(systemClock.uptimeMillis())
-            settings.putBool(RESTART_NAP_KEY, false)
-        }
+        bgExecutor.executeDelayed(
+            {
+                if (settings.getBool(RESTART_SLEEP_KEY, false)) {
+                    Log.d("RestartDozeListener", "Restarting sleep state")
+                    powerManager.wakeUp(
+                        systemClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION,
+                        "RestartDozeListener"
+                    )
+                    powerManager.goToSleep(systemClock.uptimeMillis())
+                }
+            },
+            1000
+        )
+    }
+
+    private fun storeSleepState(sleeping: Boolean) {
+        bgExecutor.execute { settings.putBool(RESTART_SLEEP_KEY, sleeping) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index eae40d6..9a90bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
@@ -34,6 +35,7 @@
 class AlternateBouncerInteractor
 @Inject
 constructor(
+    private val statusBarStateController: StatusBarStateController,
     private val keyguardStateController: KeyguardStateController,
     private val bouncerRepository: KeyguardBouncerRepository,
     private val biometricSettingsRepository: BiometricSettingsRepository,
@@ -48,6 +50,17 @@
 
     val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
 
+    private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+        object : KeyguardStateController.Callback {
+            override fun onUnlockedChanged() {
+                maybeHide()
+            }
+        }
+
+    init {
+        keyguardStateController.addCallback(keyguardStateControllerCallback)
+    }
+
     /**
      * Sets the correct bouncer states to show the alternate bouncer if it can show.
      *
@@ -107,7 +120,8 @@
                 biometricSettingsRepository.isStrongBiometricAllowed.value &&
                 biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
                 !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
-                !keyguardStateController.isUnlocked
+                !keyguardStateController.isUnlocked &&
+                !statusBarStateController.isDozing
         } else {
             legacyAlternateBouncer != null &&
                 keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
@@ -127,6 +141,12 @@
         }
     }
 
+    private fun maybeHide() {
+        if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) {
+            hide()
+        }
+    }
+
     companion object {
         private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
         private const val NOT_VISIBLE = -1L
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 95d38b1..0a3b3d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -309,6 +309,10 @@
 
     /** Tell the bouncer to start the pre hide animation. */
     fun startDisappearAnimation(runnable: Runnable) {
+        if (willRunDismissFromKeyguard()) {
+            runnable.run()
+            return
+        }
         val finishRunnable = Runnable {
             runnable.run()
             repository.setPrimaryStartDisappearAnimation(null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index b23247c..df93d23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -81,9 +81,7 @@
             )
             .map {
                 if (willRunDismissFromKeyguard) {
-                    ScrimAlpha(
-                        notificationsAlpha = 1f,
-                    )
+                    ScrimAlpha()
                 } else if (leaveShadeOpen) {
                     ScrimAlpha(
                         behindAlpha = 1f,
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 98b6d70..60192b8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -152,6 +152,14 @@
         return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
     }
 
+    /** Provides a logging buffer for logs related to Quick Settings configuration. */
+    @Provides
+    @SysUISingleton
+    @QSConfigLog
+    public static LogBuffer provideQSConfigLogBuffer(LogBufferFactory factory) {
+        return factory.create("QSConfigLog", 100 /* maxSize */, true /* systrace */);
+    }
+
     /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
new file mode 100644
index 0000000..295bf88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.plugins.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for QS configuration changed messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface QSConfigLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index c91f855..36eab39 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -646,12 +646,15 @@
                     } else {
                         mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
                         if (device.getIntent() != null) {
-                            if (device.getIntent().isActivity()) {
-                                mActivityStarter.startActivity(
-                                        device.getIntent().getIntent(), true);
+                            PendingIntent deviceIntent = device.getIntent();
+                            boolean showOverLockscreen = mKeyguardStateController.isShowing()
+                                    && mActivityIntentHelper.wouldPendingShowOverLockscreen(
+                                        deviceIntent, mLockscreenUserManager.getCurrentUserId());
+                            if (deviceIntent.isActivity() && !showOverLockscreen) {
+                                mActivityStarter.postStartActivityDismissingKeyguard(deviceIntent);
                             } else {
                                 try {
-                                    device.getIntent().send();
+                                    deviceIntent.send();
                                 } catch (PendingIntent.CanceledException e) {
                                     Log.e(TAG, "Device pending intent was canceled");
                                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index ee93c37..dbc2a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,12 +19,13 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
-import androidx.annotation.AttrRes
+import androidx.annotation.ColorRes
 import androidx.annotation.DrawableRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT
 
 /** Utility methods for media tap-to-transfer. */
 class MediaTttUtils {
@@ -78,7 +79,7 @@
                     return IconInfo(
                         contentDescription,
                         MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)),
-                        tintAttr = null,
+                        tint = null,
                         isAppIcon = true
                     )
                 } catch (e: PackageManager.NameNotFoundException) {
@@ -96,7 +97,7 @@
                     )
                 },
                 MediaTttIcon.Resource(R.drawable.ic_cast),
-                tintAttr = android.R.attr.textColorPrimary,
+                tint = DEFAULT_ICON_TINT,
                 isAppIcon = false
             )
         }
@@ -107,7 +108,7 @@
 data class IconInfo(
     val contentDescription: ContentDescription,
     val icon: MediaTttIcon,
-    @AttrRes val tintAttr: Int?,
+    @ColorRes val tint: Int?,
     /**
      * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
      */
@@ -120,7 +121,7 @@
                 is MediaTttIcon.Loaded -> Icon.Loaded(icon.drawable, contentDescription)
                 is MediaTttIcon.Resource -> Icon.Resource(icon.res, contentDescription)
             }
-        return TintedIcon(iconOutput, tintAttr)
+        return TintedIcon(iconOutput, tint)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 9bccb7d..89f66b7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -19,12 +19,11 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
+import android.view.WindowInsets.Type
 import android.view.WindowManager
-import com.android.internal.R as AndroidR
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
-import com.android.systemui.shared.system.QuickStepContract
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -62,17 +61,12 @@
         val width = windowMetrics.bounds.width()
         var height = maximumWindowHeight
 
-        // TODO(b/271410803): Read isTransientTaskbar from Launcher
         val isLargeScreen = isLargeScreen(context)
-        val isTransientTaskbar =
-            QuickStepContract.isGesturalMode(
-                context.resources.getInteger(
-                    com.android.internal.R.integer.config_navBarInteractionMode
-                )
-            )
-        if (isLargeScreen && !isTransientTaskbar) {
+        if (isLargeScreen) {
             val taskbarSize =
-                context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+                windowManager.currentWindowMetrics.windowInsets
+                    .getInsets(Type.tappableElement())
+                    .bottom
             height -= taskbarSize
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2668d2e..fdab9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -91,16 +91,19 @@
             new QSPanel.OnConfigurationChangedListener() {
                 @Override
                 public void onConfigurationChange(Configuration newConfig) {
-                    mQSLogger.logOnConfigurationChanged(
-                        /* lastOrientation= */ mLastOrientation,
-                        /* newOrientation= */ newConfig.orientation,
-                        /* containerName= */ mView.getDumpableTag());
-
-                    boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
+                    final boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
+                    final int previousOrientation = mLastOrientation;
                     mShouldUseSplitNotificationShade =
-                        LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+                            LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
                     mLastOrientation = newConfig.orientation;
 
+                    mQSLogger.logOnConfigurationChanged(
+                        /* oldOrientation= */ previousOrientation,
+                        /* newOrientation= */ mLastOrientation,
+                        /* oldShouldUseSplitShade= */ previousSplitShadeState,
+                        /* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade,
+                        /* containerName= */ mView.getDumpableTag());
+
                     switchTileLayoutIfNeeded();
                     onConfigurationChanged();
                     if (previousSplitShadeState != mShouldUseSplitNotificationShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index adc7165..91ecaea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -132,9 +132,8 @@
             mServices.remove(tile);
             mTokenMap.remove(service.getToken());
             mTiles.remove(tile.getComponent());
-            final String slot = tile.getComponent().getClassName();
-            // TileServices doesn't know how to add more than 1 icon per slot, so remove all
-            mMainHandler.post(() -> mStatusBarIconController.removeAllIconsForSlot(slot));
+            final String slot = getStatusBarIconSlotName(tile.getComponent());
+            mMainHandler.post(() -> mStatusBarIconController.removeIconForTile(slot));
         }
     }
 
@@ -308,12 +307,11 @@
                             ? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
                                     contentDescription)
                             : null;
+                    final String slot = getStatusBarIconSlotName(componentName);
                     mMainHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            StatusBarIconController iconController = mStatusBarIconController;
-                            iconController.setIcon(componentName.getClassName(), statusIcon);
-                            iconController.setExternalIcon(componentName.getClassName());
+                            mStatusBarIconController.setIconFromTile(slot, statusIcon);
                         }
                     });
                 }
@@ -373,6 +371,12 @@
         mCommandQueue.removeCallback(mRequestListeningCallback);
     }
 
+    /** Returns the slot name that should be used when adding or removing status bar icons. */
+    private String getStatusBarIconSlotName(ComponentName componentName) {
+        return componentName.getClassName();
+    }
+
+
     private final CommandQueue.Callbacks mRequestListeningCallback = new CommandQueue.Callbacks() {
         @Override
         public void requestTileServiceListeningState(@NonNull ComponentName componentName) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 23c41db..5b461a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -16,8 +16,12 @@
 
 package com.android.systemui.qs.logging
 
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.content.res.Configuration.Orientation
 import android.service.quicksettings.Tile
 import android.view.View
+import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
 import com.android.systemui.plugins.log.ConstantStringsLogger
 import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
@@ -32,8 +36,12 @@
 
 private const val TAG = "QSLog"
 
-class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) :
-    ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
+class QSLogger
+@Inject
+constructor(
+    @QSLog private val buffer: LogBuffer,
+    @QSConfigLog private val configChangedBuffer: LogBuffer,
+) : ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
 
     fun logException(@CompileTimeConstant logMsg: String, ex: Exception) {
         buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
@@ -264,19 +272,28 @@
     }
 
     fun logOnConfigurationChanged(
-        lastOrientation: Int,
-        newOrientation: Int,
+        @Orientation oldOrientation: Int,
+        @Orientation newOrientation: Int,
+        newShouldUseSplitShade: Boolean,
+        oldShouldUseSplitShade: Boolean,
         containerName: String
     ) {
-        buffer.log(
+        configChangedBuffer.log(
             TAG,
             DEBUG,
             {
                 str1 = containerName
-                int1 = lastOrientation
+                int1 = oldOrientation
                 int2 = newOrientation
+                bool1 = oldShouldUseSplitShade
+                bool2 = newShouldUseSplitShade
             },
-            { "configuration change: $str1 orientation was $int1, now $int2" }
+            {
+                "config change: " +
+                    "$str1 orientation=${toOrientationString(int2)} " +
+                    "(was ${toOrientationString(int1)}), " +
+                    "splitShade=$bool2 (was $bool1)"
+            }
         )
     }
 
@@ -353,3 +370,11 @@
         }
     }
 }
+
+private inline fun toOrientationString(@Orientation orientation: Int): String {
+    return when (orientation) {
+        ORIENTATION_LANDSCAPE -> "land"
+        ORIENTATION_PORTRAIT -> "port"
+        else -> "undefined"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 46412a3..75d0172 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -478,13 +478,13 @@
                 synchronized (mSignalCallback.mWifiInfo) {
                     mSignalCallback.mWifiInfo.copyTo(mifiInfo);
                 }
-                handleUpdateCellularState(state, mifiInfo);
+                handleUpdateWifiState(state, mifiInfo);
             } else if (mLastTileState == LAST_STATE_ETHERNET) {
                 EthernetCallbackInfo ethernetInfo = new EthernetCallbackInfo();
                 synchronized (mSignalCallback.mEthernetInfo) {
                     mSignalCallback.mEthernetInfo.copyTo(ethernetInfo);
                 }
-                handleUpdateCellularState(state, ethernetInfo);
+                handleUpdateEthernetState(state, ethernetInfo);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6ab7bfd..ad8ae75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -158,6 +158,7 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
 import com.android.systemui.plugins.qs.QS;
@@ -960,6 +961,7 @@
                 onTrackingStopped(false);
                 instantCollapse();
             } else {
+                mView.animate().cancel();
                 mView.animate()
                         .alpha(0f)
                         .setStartDelay(0)
@@ -1574,10 +1576,9 @@
                 transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
                 transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
-                boolean customClockAnimation =
-                            mKeyguardStatusViewController.getClockAnimations() != null
-                            && mKeyguardStatusViewController.getClockAnimations()
-                                    .getHasCustomPositionUpdatedAnimation();
+                ClockAnimations clockAnims = mKeyguardStatusViewController.getClockAnimations();
+                boolean customClockAnimation = clockAnims != null
+                        && clockAnims.getHasCustomPositionUpdatedAnimation();
 
                 if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
                     // Find the clock, so we can exclude it from this transition.
@@ -3087,7 +3088,9 @@
      */
     public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
             Runnable cancelAction) {
-        mView.animate()
+        final ViewPropertyAnimator viewAnimator = mView.animate();
+        viewAnimator.cancel();
+        viewAnimator
                 .translationX(0)
                 .alpha(1f)
                 .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
@@ -3106,9 +3109,14 @@
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         endAction.run();
+
+                        viewAnimator.setListener(null);
+                        viewAnimator.setUpdateListener(null);
                     }
-                }).setUpdateListener(anim -> mKeyguardStatusViewController.animateFoldToAod(
-                        anim.getAnimatedFraction())).start();
+                })
+                .setUpdateListener(anim ->
+                        mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()))
+                .start();
     }
 
     /** Cancels fold to AOD transition and resets view state. */
@@ -3307,6 +3315,7 @@
     }
 
     public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
+        mView.animate().cancel();
         return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
                 durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
                 endAction);
@@ -3556,7 +3565,13 @@
                             : (mKeyguardStateController.canDismissLockScreen()
                                     ? UNLOCK : BOUNCER_UNLOCK);
 
-            fling(vel, expand, isFalseTouch(x, y, interactionType));
+            // don't fling while in keyguard to avoid jump in shade expand animation;
+            // touch has been intercepted already so flinging here is redundant
+            if (mBarState == KEYGUARD && mExpandedFraction >= 1.0) {
+                mShadeLog.d("NPVC endMotionEvent - skipping fling on keyguard");
+            } else {
+                fling(vel, expand, isFalseTouch(x, y, interactionType));
+            }
             onTrackingStopped(expand);
             mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
             if (mUpdateFlingOnLayout) {
@@ -5027,9 +5042,14 @@
             Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
             Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
 
-            anim.addUpdateListener(
-                    animation -> mController.getClockAnimations().onPositionUpdated(
-                            from, to, animation.getAnimatedFraction()));
+            anim.addUpdateListener(animation -> {
+                ClockAnimations clockAnims = mController.getClockAnimations();
+                if (clockAnims == null) {
+                    return;
+                }
+
+                clockAnims.onPositionUpdated(from, to, animation.getAnimatedFraction());
+            });
 
             return anim;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 5adb58b..63179da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -77,12 +77,6 @@
      */
     public static void fadeOut(View view, float fadeOutAmount, boolean remap) {
         view.animate().cancel();
-
-        // Don't fade out if already not visible.
-        if (view.getAlpha() == 0.0f) {
-            return;
-        }
-
         if (fadeOutAmount == 1.0f && view.getVisibility() != View.GONE) {
             view.setVisibility(View.INVISIBLE);
         } else if (view.getVisibility() == View.INVISIBLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 04cc8ce..30d2295 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -81,28 +81,22 @@
     void refreshIconGroup(IconManager iconManager);
 
     /**
-     * Adds or updates an icon for a given slot for a **tile service icon**.
+     * Adds or updates an icon that comes from an active tile service.
      *
-     * TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method
-     *   much more clearly distinct from that method.
+     * If the icon is null, the icon will be removed.
      */
-    void setExternalIcon(String slot);
+    void setIconFromTile(String slot, @Nullable StatusBarIcon icon);
+
+    /** Removes an icon that had come from an active tile service. */
+    void removeIconForTile(String slot);
 
     /**
      * Adds or updates an icon for the given slot for **internal system icons**.
      *
-     * TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the
-     * {@link #setIcon(String, StatusBarIcon)} method.
+     * TODO(b/265307726): Re-name this to `setInternalIcon`.
      */
     void setIcon(String slot, int resourceId, CharSequence contentDescription);
 
-    /**
-     * Adds or updates an icon for the given slot for an **externally-provided icon**.
-     *
-     * TODO(b/265307726): Rename to `setExternalIcon` or something similar.
-     */
-    void setIcon(String slot, StatusBarIcon icon);
-
     /** */
     void setWifiIcon(String slot, WifiIconState state);
 
@@ -152,15 +146,10 @@
      */
     void removeIcon(String slot, int tag);
 
-    /** */
-    void removeAllIconsForSlot(String slot);
-
     /**
-     * Removes all the icons for the given slot.
-     *
-     * Only use this for icons that have come from **an external process**.
+     * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`.
      */
-    void removeAllIconsForExternalSlot(String slot);
+    void removeAllIconsForSlot(String slot);
 
     // TODO: See if we can rename this tunable name.
     String ICON_HIDE_LIST = "icon_blacklist";
@@ -618,13 +607,6 @@
             mGroup.removeAllViews();
         }
 
-        protected void onIconExternal(int viewIndex, int height) {
-            ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex);
-            imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
-            imageView.setAdjustViewBounds(true);
-            setHeightAndCenter(imageView, height);
-        }
-
         protected void onDensityOrFontScaleChanged() {
             for (int i = 0; i < mGroup.getChildCount(); i++) {
                 View child = mGroup.getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 0727c5a..3a18423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -32,7 +32,6 @@
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Dumpable;
-import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
@@ -62,7 +61,7 @@
  */
 @SysUISingleton
 public class StatusBarIconControllerImpl implements Tunable,
-        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
+        ConfigurationListener, Dumpable, StatusBarIconController, DemoMode {
 
     private static final String TAG = "StatusBarIconController";
     // Use this suffix to prevent external icon slot names from unintentionally overriding our
@@ -93,7 +92,7 @@
         mStatusBarPipelineFlags = statusBarPipelineFlags;
 
         configurationController.addCallback(this);
-        commandQueue.addCallback(this);
+        commandQueue.addCallback(mCommandQueueCallbacks);
         tunerService.addTunable(this, ICON_HIDE_LIST);
         demoModeController.addCallback(this);
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -350,26 +349,35 @@
         }
     }
 
-    @Override
-    public void setExternalIcon(String slot) {
-        String slotName = createExternalSlotName(slot);
-        int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0);
-        int height = mContext.getResources().getDimensionPixelSize(
-                R.dimen.status_bar_icon_drawing_size);
-        mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
-    }
-
-    // Override for *both* CommandQueue.Callbacks AND StatusBarIconController.
-    // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
-    //  differentiate between those callback methods and StatusBarIconController methods.
-    @Override
-    public void setIcon(String slot, StatusBarIcon icon) {
-        String slotName = createExternalSlotName(slot);
-        if (icon == null) {
-            removeAllIconsForSlot(slotName);
-            return;
+    private final CommandQueue.Callbacks mCommandQueueCallbacks = new CommandQueue.Callbacks() {
+        @Override
+        public void setIcon(String slot, StatusBarIcon icon) {
+            // Icons that come from CommandQueue are from external services.
+            setExternalIcon(slot, icon);
         }
 
+        @Override
+        public void removeIcon(String slot) {
+            removeAllIconsForExternalSlot(slot);
+        }
+    };
+
+    @Override
+    public void setIconFromTile(String slot, StatusBarIcon icon) {
+        setExternalIcon(slot, icon);
+    }
+
+    @Override
+    public void removeIconForTile(String slot) {
+        removeAllIconsForExternalSlot(slot);
+    }
+
+    private void setExternalIcon(String slot, StatusBarIcon icon) {
+        if (icon == null) {
+            removeAllIconsForExternalSlot(slot);
+            return;
+        }
+        String slotName = createExternalSlotName(slot);
         StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
         setIcon(slotName, holder);
     }
@@ -417,14 +425,6 @@
         }
     }
 
-    // CommandQueue.Callbacks override
-    // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
-    //  differentiate between those callback methods and StatusBarIconController methods.
-    @Override
-    public void removeIcon(String slot) {
-        removeAllIconsForExternalSlot(slot);
-    }
-
     /** */
     @Override
     public void removeIcon(String slot, int tag) {
@@ -444,8 +444,7 @@
         mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
     }
 
-    @Override
-    public void removeAllIconsForExternalSlot(String slotName) {
+    private void removeAllIconsForExternalSlot(String slotName) {
         removeAllIconsForSlot(createExternalSlotName(slotName));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 2dc15d0..71e2e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1452,11 +1452,13 @@
     public boolean onTouch(MotionEvent event) {
         boolean handledTouch = false;
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            final boolean downThenUp = event.getActionMasked() == MotionEvent.ACTION_UP
+                    && mAlternateBouncerReceivedDownTouch;
+            final boolean outsideTouch = event.getActionMasked() == MotionEvent.ACTION_OUTSIDE;
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 mAlternateBouncerReceivedDownTouch = true;
-            } else if (event.getAction() == MotionEvent.ACTION_UP
-                    && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()
-                    && mAlternateBouncerReceivedDownTouch) {
+            } else if ((downThenUp || outsideTouch)
+                    && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) {
                 showPrimaryBouncer(true);
             }
             handledTouch = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 2fe7145..4a15000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -453,9 +453,6 @@
     protected int adjustDisableFlags(int state) {
         boolean headsUpVisible =
                 mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
-        if (headsUpVisible) {
-            state |= DISABLE_CLOCK;
-        }
 
         if (!mKeyguardStateController.isLaunchTransitionFadingAway()
                 && !mKeyguardStateController.isKeyguardFadingAway()
@@ -473,6 +470,13 @@
             state |= DISABLE_ONGOING_CALL_CHIP;
         }
 
+        if (headsUpVisible) {
+            // Disable everything on the left side of the status bar, since the app name for the
+            // heads up notification appears there instead.
+            state |= DISABLE_CLOCK;
+            state |= DISABLE_ONGOING_CALL_CHIP;
+        }
+
         return state;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 4156fc1..73bf188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -179,6 +179,10 @@
     fun logDefaultMobileIconGroup(group: SignalIcon.MobileIconGroup) {
         buffer.log(TAG, LogLevel.INFO, { str1 = group.name }, { "defaultMobileIconGroup: $str1" })
     }
+
+    fun logOnSubscriptionsChanged() {
+        buffer.log(TAG, LogLevel.INFO, {}, { "onSubscriptionsChanged" })
+    }
 }
 
 private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f866d65..d0c6215 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -105,10 +105,14 @@
      * The reason we need to do this is because TelephonyManager limits the number of registered
      * listeners per-process, so we don't want to create a new listener for every callback.
      *
-     * A note on the design for back pressure here: We use the [coalesce] operator here to change
-     * the backpressure strategy to store exactly the last callback event of _each type_ here, as
-     * opposed to the default strategy which is to drop the oldest event (regardless of type). This
-     * means that we should never miss any single event as long as the flow has been started.
+     * A note on the design for back pressure here: We don't control _which_ telephony callback
+     * comes in first, since we register every relevant bit of information as a batch. E.g., if a
+     * downstream starts collecting on a field which is backed by
+     * [TelephonyCallback.ServiceStateListener], it's not possible for us to guarantee that _that_
+     * callback comes in -- the first callback could very well be
+     * [TelephonyCallback.DataActivityListener], which would promptly be dropped if we didn't keep
+     * it tracked. We use the [scan] operator here to track the most recent callback of _each type_
+     * here. See [TelephonyCallbackState] to see how the callbacks are stored.
      */
     private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
         val initial = TelephonyCallbackState()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index b7da3f2..991b786 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -129,6 +129,7 @@
         val callback =
             object : SubscriptionManager.OnSubscriptionsChangedListener() {
                 override fun onSubscriptionsChanged() {
+                    logger.logOnSubscriptionsChanged()
                     trySend(Unit)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 2ee5232..654ba04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -57,6 +57,8 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.annotation.concurrent.GuardedBy;
+
 /**
  * Default implementation of a {@link BatteryController}. This controller monitors for battery
  * level change events that are broadcasted by the system.
@@ -94,7 +96,10 @@
     private boolean mTestMode = false;
     @VisibleForTesting
     boolean mHasReceivedBattery = false;
+    @GuardedBy("mEstimateLock")
     private Estimate mEstimate;
+    private final Object mEstimateLock = new Object();
+
     private boolean mFetchingEstimate = false;
 
     // Use AtomicReference because we may request it from a different thread
@@ -321,7 +326,7 @@
 
     @Nullable
     private String generateTimeRemainingString() {
-        synchronized (mFetchCallbacks) {
+        synchronized (mEstimateLock) {
             if (mEstimate == null) {
                 return null;
             }
@@ -340,7 +345,7 @@
         mFetchingEstimate = true;
         mBgHandler.post(() -> {
             // Only fetch the estimate if they are enabled
-            synchronized (mFetchCallbacks) {
+            synchronized (mEstimateLock) {
                 mEstimate = null;
                 if (mEstimates.isHybridNotificationEnabled()) {
                     updateEstimate();
@@ -363,6 +368,7 @@
     }
 
     @WorkerThread
+    @GuardedBy("mEstimateLock")
     private void updateEstimate() {
         Assert.isNotMainThread();
         // if the estimate has been cached we can just use that, otherwise get a new one and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 805368c..f1269f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -398,6 +398,7 @@
         pw.println("  mFaceAuthEnabled: " + mFaceAuthEnabled);
         pw.println("  isKeyguardFadingAway: " + isKeyguardFadingAway());
         pw.println("  isKeyguardGoingAway: " + isKeyguardGoingAway());
+        pw.println("  isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway());
     }
 
     private class UpdateMonitorCallback extends KeyguardUpdateMonitorCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 125cc76..6e58f22 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,7 +18,8 @@
 
 import android.os.VibrationEffect
 import android.view.View
-import androidx.annotation.AttrRes
+import androidx.annotation.ColorRes
+import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
@@ -48,7 +49,7 @@
     override val priority: ViewPriority,
 ) : TemporaryViewInfo() {
     companion object {
-        @AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
+        @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a5f90f8..b15ac39 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
@@ -365,6 +366,12 @@
         assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility());
     }
 
+    @Test
+    public void testGetClockAnimations_nullClock_returnsNull() {
+        when(mClockEventController.getClock()).thenReturn(null);
+        assertNull(mController.getClockAnimations());
+    }
+
     private void verifyAttachment(VerificationMode times) {
         verify(mClockRegistry, times).registerClockChangeListener(
                 any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 26d20c2..d492758 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -196,6 +196,7 @@
                 .thenReturn(mKeyguardMessageAreaController);
         when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         mKeyguardPasswordViewController = new KeyguardPasswordViewController(
                 (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
                 SecurityMode.Password, mLockPatternUtils, null,
@@ -552,6 +553,22 @@
     }
 
     @Test
+    public void testSecurityCallbackFinish() {
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUserUnlocked(0)).thenReturn(true);
+        mKeyguardSecurityContainerController.finish(true, 0);
+        verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
+    }
+
+    @Test
+    public void testSecurityCallbackFinish_cannotDismissLockScreenAndNotStrongAuth() {
+        when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+        mKeyguardSecurityContainerController.finish(false, 0);
+        verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
+    }
+
+    @Test
     public void testOnStartingToHide() {
         mKeyguardSecurityContainerController.onStartingToHide();
         verify(mInputViewController).onStartingToHide();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4c92edd..b1051af 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2245,6 +2245,26 @@
     }
 
     @Test
+    public void assistantVisible_requestActiveUnlock() {
+        // GIVEN active unlock requests from the assistant are allowed
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT)).thenReturn(true);
+
+        // GIVEN should trigger active unlock
+        keyguardIsVisible();
+        keyguardNotGoingAway();
+        statusBarShadeIsNotLocked();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // WHEN the assistant is visible
+        mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+        // THEN request unlock with keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
+    @Test
     public void fingerprintFailure_requestActiveUnlock_dismissKeyguard()
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock
@@ -2433,6 +2453,57 @@
     }
 
     @Test
+    public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        keyguardIsVisible();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on wakeup
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
+                .thenReturn(true);
+
+        // GIVEN an unfold should force dismiss the keyguard
+        when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(
+                PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(true);
+
+        // WHEN device posture changes to unfold
+        deviceInPostureStateOpened();
+        mTestableLooper.processAllMessages();
+
+        // THEN request unlock with a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
+
+    @Test
+    public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
+        keyguardIsVisible();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on wakeup
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
+                .thenReturn(true);
+
+        // GIVEN an unfold should NOT force dismiss the keyguard
+        when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(
+                PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(false);
+
+        // WHEN device posture changes to unfold
+        deviceInPostureStateOpened();
+        mTestableLooper.processAllMessages();
+
+        // THEN request unlock WITHOUT a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(false));
+    }
+
+    @Test
     public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() {
         ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor =
                 ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 21191db..0ab675c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,7 +99,6 @@
 
     @JvmField @Rule var rule = MockitoJUnit.rule()
 
-    @Mock lateinit var keyguardStateController: KeyguardStateController
     @Mock lateinit var layoutInflater: LayoutInflater
     @Mock lateinit var fingerprintManager: FingerprintManager
     @Mock lateinit var windowManager: WindowManager
@@ -138,7 +138,8 @@
         keyguardBouncerRepository = FakeKeyguardBouncerRepository()
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
-                keyguardStateController,
+                mock(StatusBarStateController::class.java),
+                mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
                 FakeBiometricSettingsRepository(),
                 FakeDeviceEntryFingerprintAuthRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 786cb01..cefa9b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -93,6 +94,7 @@
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
+                mock(StatusBarStateController::class.java),
                 mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
                 mock(BiometricSettingsRepository::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 9d5b94b..6ca4dcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -201,6 +201,56 @@
     }
 
     @Test
+    fun testSingleAppHeaderIsNotClickable() {
+        mockLayoutInflater()
+        val packageName = "pkg"
+        `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
+        val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val header: View = parent.requireViewById(R.id.controls_header)
+        assertThat(header.isClickable).isFalse()
+        assertThat(header.hasOnClickListeners()).isFalse()
+    }
+
+    @Test
+    fun testMultipleAppHeaderIsClickable() {
+        mockLayoutInflater()
+        val packageName1 = "pkg"
+        val panel1 = SelectedItem.PanelItem("App name 1", ComponentName(packageName1, "cls"))
+        val serviceInfo1 = setUpPanel(panel1)
+
+        val packageName2 = "pkg"
+        val panel2 = SelectedItem.PanelItem("App name 2", ComponentName(packageName2, "cls"))
+        val serviceInfo2 = setUpPanel(panel2)
+
+        `when`(authorizedPanelsRepository.getAuthorizedPanels())
+                .thenReturn(setOf(packageName1, packageName2))
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo1, serviceInfo2))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val header: View = parent.requireViewById(R.id.controls_header)
+        assertThat(header.isClickable).isTrue()
+        assertThat(header.hasOnClickListeners()).isTrue()
+    }
+
+    @Test
     fun testPanelControllerStartActivityWithCorrectArguments() {
         mockLayoutInflater()
         val packageName = "pkg"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index a636b7f..b87647e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -408,6 +408,17 @@
     }
 
     @Test
+    public void testPulsing_dozeSuspendTriggers_pulseDone_doesntCrash() {
+        mMachine.requestState(INITIALIZED);
+
+        mMachine.requestState(DOZE);
+        mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
+        mMachine.requestState(DOZE_PULSING);
+        mMachine.requestState(DOZE_SUSPEND_TRIGGERS);
+        mMachine.requestState(DOZE_PULSE_DONE);
+    }
+
+    @Test
     public void testSuppressingPulse_doesntCrash() {
         mMachine.requestState(INITIALIZED);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 19347c7..58eb7d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -21,9 +21,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.DreamManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -50,6 +52,9 @@
     @Mock
     Condition.Callback mCallback;
 
+    @Mock
+    DreamManager mDreamManager;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -59,29 +64,39 @@
      * Ensure a dreaming state immediately triggers the condition.
      */
     @Test
-    public void testInitialState() {
-        final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
-        when(mContext.registerReceiver(any(), any())).thenReturn(intent);
-        final DreamCondition condition = new DreamCondition(mContext);
+    public void testInitialDreamingState() {
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
         condition.addCallback(mCallback);
-        condition.start();
 
         verify(mCallback).onConditionChanged(eq(condition));
         assertThat(condition.isConditionMet()).isTrue();
     }
 
     /**
+     * Ensure a non-dreaming state does not trigger the condition.
+     */
+    @Test
+    public void testInitialNonDreamingState() {
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
+        condition.addCallback(mCallback);
+
+        verify(mCallback, never()).onConditionChanged(eq(condition));
+        assertThat(condition.isConditionMet()).isFalse();
+    }
+
+    /**
      * Ensure that changing dream state triggers condition.
      */
     @Test
     public void testChange() {
-        final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
         final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent);
-        final DreamCondition condition = new DreamCondition(mContext);
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
         condition.addCallback(mCallback);
-        condition.start();
+        verify(mContext).registerReceiver(receiverCaptor.capture(), any());
         clearInvocations(mCallback);
         receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED));
         verify(mCallback).onConditionChanged(eq(condition));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
index de0e511..e287f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
@@ -20,12 +20,16 @@
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.never
@@ -41,13 +45,14 @@
     @Mock lateinit var statusBarStateController: StatusBarStateController
     @Mock lateinit var powerManager: PowerManager
     val clock = FakeSystemClock()
+    val executor = FakeExecutor(clock)
     lateinit var listener: StatusBarStateController.StateListener
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         restartDozeListener =
-            RestartDozeListener(settings, statusBarStateController, powerManager, clock)
+            RestartDozeListener(settings, statusBarStateController, powerManager, clock, executor)
 
         val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java)
         restartDozeListener.init()
@@ -56,30 +61,37 @@
     }
 
     @Test
-    fun testStoreDreamState_onDreamingStarted() {
-        listener.onDreamingChanged(true)
-        assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isTrue()
+    fun testStoreDreamState_onDozingStarted() {
+        listener.onDozingChanged(true)
+        executor.runAllReady()
+        assertThat(settings.getBool(RestartDozeListener.RESTART_SLEEP_KEY)).isTrue()
     }
 
     @Test
-    fun testStoreDreamState_onDreamingStopped() {
-        listener.onDreamingChanged(false)
-        assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isFalse()
+    fun testStoreDozeState_onDozingStopped() {
+        listener.onDozingChanged(false)
+        executor.runAllReady()
+        assertThat(settings.getBool(RestartDozeListener.RESTART_SLEEP_KEY)).isFalse()
     }
 
     @Test
-    fun testRestoreDreamState_dreamingShouldStart() {
-        settings.putBool(RestartDozeListener.RESTART_NAP_KEY, true)
+    fun testRestoreDozeState_dozingShouldStart() {
+        settings.putBool(RestartDozeListener.RESTART_SLEEP_KEY, true)
         restartDozeListener.maybeRestartSleep()
-        verify(powerManager).wakeUp(clock.uptimeMillis())
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        verify(powerManager)
+            .wakeUp(eq(clock.uptimeMillis()), eq(PowerManager.WAKE_REASON_APPLICATION), anyString())
         verify(powerManager).goToSleep(clock.uptimeMillis())
     }
 
     @Test
-    fun testRestoreDreamState_dreamingShouldNot() {
-        settings.putBool(RestartDozeListener.RESTART_NAP_KEY, false)
+    fun testRestoreDozeState_dozingShouldNotStart() {
+        settings.putBool(RestartDozeListener.RESTART_SLEEP_KEY, false)
         restartDozeListener.maybeRestartSleep()
-        verify(powerManager, never()).wakeUp(anyLong())
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString())
         verify(powerManager, never()).goToSleep(anyLong())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 1365132..86246f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -39,8 +40,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -52,6 +55,7 @@
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
     private lateinit var deviceEntryFingerprintAuthRepository:
         FakeDeviceEntryFingerprintAuthRepository
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var systemClock: SystemClock
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -73,6 +77,7 @@
         featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
         underTest =
             AlternateBouncerInteractor(
+                statusBarStateController,
                 keyguardStateController,
                 bouncerRepository,
                 biometricSettingsRepository,
@@ -130,6 +135,14 @@
     }
 
     @Test
+    fun canShowAlternateBouncerForFingerprint_isDozing() {
+        givenCanShowAlternateBouncer()
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
     fun show_whenCanShow() {
         givenCanShowAlternateBouncer()
 
@@ -169,6 +182,42 @@
         assertFalse(bouncerRepository.alternateBouncerVisible.value)
     }
 
+    @Test
+    fun onUnlockedIsFalse_doesNotHide() {
+        // GIVEN alternate bouncer is showing
+        bouncerRepository.setAlternateVisible(true)
+
+        val keyguardStateControllerCallbackCaptor =
+            ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
+        verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
+
+        // WHEN isUnlocked=false
+        givenCanShowAlternateBouncer()
+        whenever(keyguardStateController.isUnlocked).thenReturn(false)
+        keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
+
+        // THEN the alternate bouncer is still visible
+        assertTrue(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun onUnlockedChangedIsTrue_hide() {
+        // GIVEN alternate bouncer is showing
+        bouncerRepository.setAlternateVisible(true)
+
+        val keyguardStateControllerCallbackCaptor =
+            ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
+        verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
+
+        // WHEN isUnlocked=true
+        givenCanShowAlternateBouncer()
+        whenever(keyguardStateController.isUnlocked).thenReturn(true)
+        keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
+
+        // THEN the alternate bouncer is hidden
+        assertFalse(bouncerRepository.alternateBouncerVisible.value)
+    }
+
     private fun givenCanShowAlternateBouncer() {
         bouncerRepository.setAlternateBouncerUIAvailable(true)
         biometricSettingsRepository.setFingerprintEnrolled(true)
@@ -176,6 +225,7 @@
         biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
         deviceEntryFingerprintAuthRepository.setLockedOut(false)
         whenever(keyguardStateController.isUnlocked).thenReturn(false)
+        whenever(statusBarStateController.isDozing).thenReturn(false)
     }
 
     private fun givenCannotShowAlternateBouncer() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 5ec6283..bdc33f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -50,7 +51,6 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -90,9 +90,9 @@
                 keyguardUpdateMonitor,
                 keyguardBypassController,
             )
-        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        `when`(repository.primaryBouncerShow.value).thenReturn(false)
-        `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
+        whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
         resources = context.orCreateTestableResources
     }
 
@@ -118,7 +118,7 @@
 
     @Test
     fun testShow_keyguardIsDone() {
-        `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+        whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
         verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
         verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
     }
@@ -135,7 +135,7 @@
 
     @Test
     fun testExpansion() {
-        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
         underTest.setPanelExpansion(0.6f)
         verify(repository).setPanelExpansion(0.6f)
         verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
@@ -143,8 +143,8 @@
 
     @Test
     fun testExpansion_fullyShown() {
-        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         underTest.setPanelExpansion(EXPANSION_VISIBLE)
         verify(falsingCollector).onBouncerShown()
         verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
@@ -152,8 +152,8 @@
 
     @Test
     fun testExpansion_fullyHidden() {
-        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         underTest.setPanelExpansion(EXPANSION_HIDDEN)
         verify(repository).setPrimaryShow(false)
         verify(falsingCollector).onBouncerHidden()
@@ -163,7 +163,7 @@
 
     @Test
     fun testExpansion_startingToHide() {
-        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
         underTest.setPanelExpansion(0.1f)
         verify(repository).setPrimaryStartingToHide(true)
         verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
@@ -228,7 +228,21 @@
     }
 
     @Test
-    fun testStartDisappearAnimation() {
+    fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
+
+        val runnable = mock(Runnable::class.java)
+        underTest.startDisappearAnimation(runnable)
+        // End runnable should run immediately
+        verify(runnable).run()
+        // ... while the disappear animation should never be run
+        verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
+
         val runnable = mock(Runnable::class.java)
         underTest.startDisappearAnimation(runnable)
         verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
@@ -236,45 +250,45 @@
 
     @Test
     fun testIsFullShowing() {
-        `when`(repository.primaryBouncerShow.value).thenReturn(true)
-        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.primaryBouncerShow.value).thenReturn(true)
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         assertThat(underTest.isFullyShowing()).isTrue()
-        `when`(repository.primaryBouncerShow.value).thenReturn(false)
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
         assertThat(underTest.isFullyShowing()).isFalse()
     }
 
     @Test
     fun testIsScrimmed() {
-        `when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
         assertThat(underTest.isScrimmed()).isTrue()
-        `when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
         assertThat(underTest.isScrimmed()).isFalse()
     }
 
     @Test
     fun testIsInTransit() {
-        `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
         assertThat(underTest.isInTransit()).isTrue()
-        `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
         assertThat(underTest.isInTransit()).isFalse()
-        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
         assertThat(underTest.isInTransit()).isTrue()
     }
 
     @Test
     fun testIsAnimatingAway() {
-        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
         assertThat(underTest.isAnimatingAway()).isTrue()
-        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         assertThat(underTest.isAnimatingAway()).isFalse()
     }
 
     @Test
     fun testWillDismissWithAction() {
-        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
         assertThat(underTest.willDismissWithAction()).isTrue()
-        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
         assertThat(underTest.willDismissWithAction()).isFalse()
     }
 
@@ -363,12 +377,13 @@
         isUnlockingWithFpAllowed: Boolean,
         isAnimatingAway: Boolean
     ) {
-        `when`(repository.primaryBouncerShow.value).thenReturn(isVisible)
+        whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
         resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
-        `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(fpsDetectionRunning)
-        `when`(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
             .thenReturn(isUnlockingWithFpAllowed)
-        `when`(repository.primaryBouncerStartingDisappearAnimation.value)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value)
             .thenReturn(if (isAnimatingAway) Runnable {} else null)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 746f668..98794fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -115,7 +115,7 @@
             repository.sendTransitionStep(step(1f))
 
             assertThat(values.size).isEqualTo(4)
-            values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) }
+            values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
 
             job.cancel()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index df13fdd..eba0591 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -2350,6 +2350,48 @@
         }
     }
 
+    @Test
+    fun outputSwitcher_hasCustomIntent_openOverLockscreen() {
+        // When the device for a media player has an intent that opens over lockscreen
+        val pendingIntent = mock(PendingIntent::class.java)
+        whenever(pendingIntent.isActivity).thenReturn(true)
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())).thenReturn(true)
+
+        val customDevice = device.copy(intent = pendingIntent)
+        val dataWithDevice = mediaData.copy(device = customDevice)
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(dataWithDevice, KEY)
+
+        // When the user taps on the output switcher,
+        seamless.callOnClick()
+
+        // Then we send the pending intent as is, without modifying the original intent
+        verify(pendingIntent).send()
+        verify(pendingIntent, never()).getIntent()
+    }
+
+    @Test
+    fun outputSwitcher_hasCustomIntent_requiresUnlock() {
+        // When the device for a media player has an intent that cannot open over lockscreen
+        val pendingIntent = mock(PendingIntent::class.java)
+        whenever(pendingIntent.isActivity).thenReturn(true)
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
+            .thenReturn(false)
+
+        val customDevice = device.copy(intent = pendingIntent)
+        val dataWithDevice = mediaData.copy(device = customDevice)
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(dataWithDevice, KEY)
+
+        // When the user taps on the output switcher,
+        seamless.callOnClick()
+
+        // Then we request keyguard dismissal
+        verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
+    }
+
     private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
         withArgCaptor {
             verify(seekBarViewModel).setScrubbingChangeListener(capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 85e8d07..6c3d6f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -140,6 +141,7 @@
                 context.getString(R.string.media_transfer_receiver_content_description_unknown_app)
             )
         assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
+        assertThat(iconInfo.tint).isEqualTo(DEFAULT_ICON_TINT)
     }
 
     @Test
@@ -232,40 +234,40 @@
     fun iconInfo_toTintedIcon_loaded() {
         val contentDescription = ContentDescription.Loaded("test")
         val drawable = context.getDrawable(R.drawable.ic_cake)!!
-        val tintAttr = android.R.attr.textColorTertiary
+        val tint = R.color.GM2_blue_500
 
         val iconInfo =
             IconInfo(
                 contentDescription,
                 MediaTttIcon.Loaded(drawable),
-                tintAttr,
+                tint,
                 isAppIcon = false,
             )
 
         val tinted = iconInfo.toTintedIcon()
 
         assertThat(tinted.icon).isEqualTo(Icon.Loaded(drawable, contentDescription))
-        assertThat(tinted.tintAttr).isEqualTo(tintAttr)
+        assertThat(tinted.tint).isEqualTo(tint)
     }
 
     @Test
     fun iconInfo_toTintedIcon_resource() {
         val contentDescription = ContentDescription.Loaded("test")
         val drawableRes = R.drawable.ic_cake
-        val tintAttr = android.R.attr.textColorTertiary
+        val tint = R.color.GM2_blue_500
 
         val iconInfo =
             IconInfo(
                 contentDescription,
                 MediaTttIcon.Resource(drawableRes),
-                tintAttr,
+                tint,
                 isAppIcon = false
             )
 
         val tinted = iconInfo.toTintedIcon()
 
         assertThat(tinted.icon).isEqualTo(Icon.Resource(drawableRes, contentDescription))
-        assertThat(tinted.tintAttr).isEqualTo(tintAttr)
+        assertThat(tinted.tint).isEqualTo(tint)
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index 464acb6..01ffdcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -19,12 +19,14 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.content.res.Resources
+import android.graphics.Insets
 import android.graphics.Rect
 import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowMetrics
+import androidx.core.view.WindowInsetsCompat.Type
 import androidx.test.filters.SmallTest
-import com.android.internal.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.statusbar.policy.FakeConfigurationController
@@ -94,7 +96,13 @@
     }
 
     private fun givenTaskbarSize(size: Int) {
-        whenever(resources.getDimensionPixelSize(eq(R.dimen.taskbar_frame_height))).thenReturn(size)
+        val windowInsets =
+            WindowInsets.Builder()
+                .setInsets(Type.tappableElement(), Insets.of(Rect(0, 0, 0, size)))
+                .build()
+        val windowMetrics = WindowMetrics(windowManager.maximumWindowMetrics.bounds, windowInsets)
+        whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
+        whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
     }
 
     private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 99cf8d0..7087c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -24,7 +24,9 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
@@ -184,6 +186,7 @@
     @Mock protected NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock protected KeyguardBottomAreaView mKeyguardBottomArea;
     @Mock protected KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
+    @Mock protected ViewPropertyAnimator mViewPropertyAnimator;
     @Mock protected KeyguardBottomAreaView mQsFrame;
     @Mock protected HeadsUpManagerPhone mHeadsUpManager;
     @Mock protected NotificationShelfController mNotificationShelfController;
@@ -357,7 +360,14 @@
                 .thenReturn(mHeadsUpCallback);
         when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea);
         when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
-        when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
+        when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator);
+        when(mView.animate()).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.setInterpolator(any())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator);
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 4517596..b868018 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -42,6 +42,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -674,6 +676,24 @@
     }
 
     @Test
+    public void testFoldToAodAnimationCleansupInAnimationEnd() {
+        ArgumentCaptor<Animator.AnimatorListener> animCaptor =
+                ArgumentCaptor.forClass(Animator.AnimatorListener.class);
+        ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> updateCaptor =
+                ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+
+        // Start fold animation & Capture Listeners
+        mNotificationPanelViewController.startFoldToAodAnimation(() -> {}, () -> {}, () -> {});
+        verify(mViewPropertyAnimator).setListener(animCaptor.capture());
+        verify(mViewPropertyAnimator).setUpdateListener(updateCaptor.capture());
+
+        // End animation and validate listeners were unset
+        animCaptor.getValue().onAnimationEnd(null);
+        verify(mViewPropertyAnimator).setListener(null);
+        verify(mViewPropertyAnimator).setUpdateListener(null);
+    }
+
+    @Test
     public void testExpandWithQsMethodIsUsingLockscreenTransitionController() {
         enableSplitShade(/* enabled= */ true);
         mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
index 3108ed9..fe12051 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -49,6 +50,13 @@
     }
 
     @Test
+    public void userSwitcherChip_defaultVisibilityIsGone() {
+        assertThat(mKeyguardStatusBarView.findViewById(
+                R.id.user_switcher_container).getVisibility()).isEqualTo(
+                View.GONE);
+    }
+
+    @Test
     public void setTopClipping_clippingUpdated() {
         int topClipping = 40;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
index 3bc288a2..08e89fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -20,13 +20,17 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.StatusBarIcon
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.mockito.Mock
 import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 class StatusBarIconControllerImplTest : SysuiTestCase() {
@@ -34,15 +38,19 @@
     private lateinit var underTest: StatusBarIconControllerImpl
 
     private lateinit var iconList: StatusBarIconList
+    private lateinit var commandQueueCallbacks: CommandQueue.Callbacks
     private val iconGroup: StatusBarIconController.IconManager = mock()
 
+    @Mock private lateinit var commandQueue: CommandQueue
+
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
         iconList = StatusBarIconList(arrayOf())
         underTest =
             StatusBarIconControllerImpl(
                 context,
-                mock(),
+                commandQueue,
                 mock(),
                 mock(),
                 mock(),
@@ -51,11 +59,14 @@
                 mock(),
             )
         underTest.addIconGroup(iconGroup)
+        val commandQueueCallbacksCaptor = kotlinArgumentCaptor<CommandQueue.Callbacks>()
+        verify(commandQueue).addCallback(commandQueueCallbacksCaptor.capture())
+        commandQueueCallbacks = commandQueueCallbacksCaptor.value
     }
 
     /** Regression test for b/255428281. */
     @Test
-    fun internalAndExternalIconWithSameName_bothDisplayed() {
+    fun internalAndExternalIconWithSameName_externalFromTile_bothDisplayed() {
         val slotName = "mute"
 
         // Internal
@@ -71,7 +82,7 @@
                 /* number= */ 0,
                 "contentDescription",
             )
-        underTest.setIcon(slotName, externalIcon)
+        underTest.setIconFromTile(slotName, externalIcon)
 
         assertThat(iconList.slots).hasSize(2)
         // Whichever was added last comes first
@@ -83,17 +94,45 @@
 
     /** Regression test for b/255428281. */
     @Test
-    fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() {
+    fun internalAndExternalIconWithSameName_externalFromCommandQueue_bothDisplayed() {
         val slotName = "mute"
 
         // Internal
         underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
 
         // External
-        underTest.setIcon(slotName, createExternalIcon())
+        val externalIcon =
+            StatusBarIcon(
+                "external.package",
+                UserHandle.ALL,
+                /* iconId= */ 2,
+                /* iconLevel= */ 0,
+                /* number= */ 0,
+                "contentDescription",
+            )
+        commandQueueCallbacks.setIcon(slotName, externalIcon)
 
-        // WHEN the external icon is removed via #removeIcon
-        underTest.removeIcon(slotName)
+        assertThat(iconList.slots).hasSize(2)
+        // Whichever was added last comes first
+        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(iconList.slots[1].name).isEqualTo(slotName)
+        assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+        assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_externalRemoved_fromCommandQueue_internalStays() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        commandQueueCallbacks.setIcon(slotName, createExternalIcon())
+
+        // WHEN the external icon is removed via CommandQueue.Callbacks#removeIcon
+        commandQueueCallbacks.removeIcon(slotName)
 
         // THEN the external icon is removed but the internal icon remains
         // Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for
@@ -109,17 +148,17 @@
 
     /** Regression test for b/255428281. */
     @Test
-    fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() {
+    fun internalAndExternalIconWithSameName_externalRemoved_fromTileRemove_internalStays() {
         val slotName = "mute"
 
         // Internal
         underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
 
         // External
-        underTest.setIcon(slotName, createExternalIcon())
+        underTest.setIconFromTile(slotName, createExternalIcon())
 
-        // WHEN the external icon is removed via #removeAllIconsForExternalSlot
-        underTest.removeAllIconsForExternalSlot(slotName)
+        // WHEN the external icon is removed via #removeIconForTile
+        underTest.removeIconForTile(slotName)
 
         // THEN the external icon is removed but the internal icon remains
         assertThat(iconList.slots).hasSize(2)
@@ -133,17 +172,17 @@
 
     /** Regression test for b/255428281. */
     @Test
-    fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() {
+    fun internalAndExternalIconWithSameName_externalRemoved_fromTileSetNull_internalStays() {
         val slotName = "mute"
 
         // Internal
         underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
 
         // External
-        underTest.setIcon(slotName, createExternalIcon())
+        underTest.setIconFromTile(slotName, createExternalIcon())
 
-        // WHEN the external icon is removed via a #setIcon(null)
-        underTest.setIcon(slotName, /* icon= */ null)
+        // WHEN the external icon is removed via a #setIconFromTile(null)
+        underTest.setIconFromTile(slotName, /* icon= */ null)
 
         // THEN the external icon is removed but the internal icon remains
         assertThat(iconList.slots).hasSize(2)
@@ -164,12 +203,12 @@
         underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
 
         // External
-        underTest.setIcon(slotName, createExternalIcon())
+        underTest.setIconFromTile(slotName, createExternalIcon())
 
         // WHEN the internal icon is removed via #removeIcon
         underTest.removeIcon(slotName, /* tag= */ 0)
 
-        // THEN the external icon is removed but the internal icon remains
+        // THEN the internal icon is removed but the external icon remains
         assertThat(iconList.slots).hasSize(2)
         assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
         assertThat(iconList.slots[1].name).isEqualTo(slotName)
@@ -188,12 +227,12 @@
         underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
 
         // External
-        underTest.setIcon(slotName, createExternalIcon())
+        underTest.setIconFromTile(slotName, createExternalIcon())
 
         // WHEN the internal icon is removed via #removeAllIconsForSlot
         underTest.removeAllIconsForSlot(slotName)
 
-        // THEN the external icon is removed but the internal icon remains
+        // THEN the internal icon is removed but the external icon remains
         assertThat(iconList.slots).hasSize(2)
         assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
         assertThat(iconList.slots[1].name).isEqualTo(slotName)
@@ -221,7 +260,7 @@
                 /* number= */ 0,
                 "externalDescription",
             )
-        underTest.setIcon(slotName, startingExternalIcon)
+        underTest.setIconFromTile(slotName, startingExternalIcon)
 
         // WHEN the internal icon is updated
         underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription")
@@ -243,7 +282,7 @@
 
     /** Regression test for b/255428281. */
     @Test
-    fun internalAndExternalIconWithSameName_externalUpdatedIndependently() {
+    fun internalAndExternalIconWithSameName_fromTile_externalUpdatedIndependently() {
         val slotName = "mute"
 
         // Internal
@@ -259,7 +298,7 @@
                 /* number= */ 0,
                 "externalDescription",
             )
-        underTest.setIcon(slotName, startingExternalIcon)
+        underTest.setIconFromTile(slotName, startingExternalIcon)
 
         // WHEN the external icon is updated
         val newExternalIcon =
@@ -271,7 +310,54 @@
                 /* number= */ 0,
                 "newExternalDescription",
             )
-        underTest.setIcon(slotName, newExternalIcon)
+        underTest.setIconFromTile(slotName, newExternalIcon)
+
+        // THEN only the external slot gets the updates
+        val externalSlot = iconList.slots[0]
+        val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
+        assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+        assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription")
+        assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21)
+
+        // And the internal slot has its own values
+        val internalSlot = iconList.slots[1]
+        val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
+        assertThat(internalSlot.name).isEqualTo(slotName)
+        assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription")
+        assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10)
+    }
+
+    /** Regression test for b/255428281. */
+    @Test
+    fun internalAndExternalIconWithSameName_fromCommandQueue_externalUpdatedIndependently() {
+        val slotName = "mute"
+
+        // Internal
+        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+        // External
+        val startingExternalIcon =
+            StatusBarIcon(
+                "external.package",
+                UserHandle.ALL,
+                /* iconId= */ 20,
+                /* iconLevel= */ 0,
+                /* number= */ 0,
+                "externalDescription",
+            )
+        commandQueueCallbacks.setIcon(slotName, startingExternalIcon)
+
+        // WHEN the external icon is updated
+        val newExternalIcon =
+            StatusBarIcon(
+                "external.package",
+                UserHandle.ALL,
+                /* iconId= */ 21,
+                /* iconLevel= */ 0,
+                /* number= */ 0,
+                "newExternalDescription",
+            )
+        commandQueueCallbacks.setIcon(slotName, newExternalIcon)
 
         // THEN only the external slot gets the updates
         val externalSlot = iconList.slots[0]
@@ -289,8 +375,16 @@
     }
 
     @Test
-    fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() {
-        underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
+    fun externalSlot_fromTile_alreadyEndsWithSuffix_suffixNotAddedTwice() {
+        underTest.setIconFromTile("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
+
+        assertThat(iconList.slots).hasSize(1)
+        assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
+    }
+
+    @Test
+    fun externalSlot_fromCommandQueue_alreadyEndsWithSuffix_suffixNotAddedTwice() {
+        commandQueueCallbacks.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
 
         assertThat(iconList.slots).hasSize(1)
         assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 61286a4..346b90c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -786,4 +786,20 @@
         // THEN the alternateBouncer doesn't hide
         verify(mAlternateBouncerInteractor, never()).hide();
     }
+
+    @Test
+    public void testAlternateBouncerOnTouch_actionOutside_hidesAlternateBouncer() {
+        reset(mAlternateBouncerInteractor);
+
+        // GIVEN the alternate bouncer has shown for a minimum amount of time
+        when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // WHEN only ACTION_OUTSIDE touch event comes
+        mStatusBarKeyguardViewManager.onTouch(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_OUTSIDE, 0f, 0f, 0));
+
+        // THEN the alternateBouncer hides
+        verify(mAlternateBouncerInteractor).hide();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 1e5782b..49c50d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -294,6 +294,13 @@
     }
 
     @Test
+    public void userChip_defaultVisibilityIsGone() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        assertEquals(View.GONE, getUserChipView().getVisibility());
+    }
+
+    @Test
     public void disable_noOngoingCall_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -333,6 +340,19 @@
     }
 
     @Test
+    public void disable_hasOngoingCallButAlsoHun_chipHidden() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
+        when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        assertEquals(View.GONE,
+                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+    }
+
+    @Test
     public void disable_ongoingCallEnded_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -558,6 +578,10 @@
         return (CollapsedStatusBarFragment) mFragment;
     }
 
+    private View getUserChipView() {
+        return mFragment.getView().findViewById(R.id.user_switcher_container);
+    }
+
     private View getClockView() {
         return mFragment.getView().findViewById(R.id.clock);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 542b688..934e1c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -25,7 +25,6 @@
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.DataActivityListener
 import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
@@ -68,6 +67,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -392,13 +392,17 @@
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
-            val type = NETWORK_TYPE_UNKNOWN
-            val expected = UnknownNetworkType
-            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            val ti =
+                telephonyDisplayInfo(
+                    networkType = NETWORK_TYPE_UNKNOWN,
+                    overrideNetworkType = NETWORK_TYPE_UNKNOWN,
+                )
+
             callback.onDisplayInfoChanged(ti)
 
+            val expected = UnknownNetworkType
             assertThat(latest).isEqualTo(expected)
-            assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type))
+            assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN))
 
             job.cancel()
         }
@@ -412,14 +416,10 @@
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val overrideType = OVERRIDE_NETWORK_TYPE_NONE
             val type = NETWORK_TYPE_LTE
-            val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
-            val ti =
-                mock<TelephonyDisplayInfo>().also {
-                    whenever(it.overrideNetworkType).thenReturn(overrideType)
-                    whenever(it.networkType).thenReturn(type)
-                }
+            val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = overrideType)
             callback.onDisplayInfoChanged(ti)
 
+            val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
             assertThat(latest).isEqualTo(expected)
 
             job.cancel()
@@ -433,14 +433,10 @@
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val type = OVERRIDE_NETWORK_TYPE_LTE_CA
-            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
-            val ti =
-                mock<TelephonyDisplayInfo>().also {
-                    whenever(it.networkType).thenReturn(type)
-                    whenever(it.overrideNetworkType).thenReturn(type)
-                }
+            val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = type)
             callback.onDisplayInfoChanged(ti)
 
+            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
             assertThat(latest).isEqualTo(expected)
 
             job.cancel()
@@ -455,14 +451,10 @@
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val unknown = NETWORK_TYPE_UNKNOWN
             val type = OVERRIDE_NETWORK_TYPE_LTE_CA
-            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
-            val ti =
-                mock<TelephonyDisplayInfo>().also {
-                    whenever(it.networkType).thenReturn(unknown)
-                    whenever(it.overrideNetworkType).thenReturn(type)
-                }
+            val ti = telephonyDisplayInfo(unknown, type)
             callback.onDisplayInfoChanged(ti)
 
+            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
             assertThat(latest).isEqualTo(expected)
 
             job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index bbf04ed2..9da9ff7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -64,10 +64,14 @@
  *
  * Kind of like an interaction test case build just for [TelephonyCallback]
  *
- * The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener]
- * [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener]
- * [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener]
- * [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener]
+ * The list of telephony callbacks we use is:
+ * - [TelephonyCallback.CarrierNetworkListener]
+ * - [TelephonyCallback.DataActivityListener]
+ * - [TelephonyCallback.DataConnectionStateListener]
+ * - [TelephonyCallback.DataEnabledListener]
+ * - [TelephonyCallback.DisplayInfoListener]
+ * - [TelephonyCallback.ServiceStateListener]
+ * - [TelephonyCallback.SignalStrengthsListener]
  *
  * Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed
  * by only a single callback can immediately create backpressure on the other fields related to a
@@ -201,7 +205,6 @@
                 200 /* unused */
             )
 
-            // Send a bunch of events that we don't care about, to overrun the replay buffer
             flipActivity(100, activityCallback)
 
             val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -225,7 +228,6 @@
 
             enabledCallback.onDataEnabledChanged(true, 1 /* unused */)
 
-            // Send a bunch of events that we don't care about, to overrun the replay buffer
             flipActivity(100, activityCallback)
 
             val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
@@ -252,7 +254,6 @@
             val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
             displayInfoCallback.onDisplayInfoChanged(ti)
 
-            // Send a bunch of events that we don't care about, to overrun the replay buffer
             flipActivity(100, activityCallback)
 
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index d07b96f..cf815c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -19,6 +19,7 @@
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -48,6 +49,12 @@
         return signalStrength
     }
 
+    fun telephonyDisplayInfo(networkType: Int, overrideNetworkType: Int) =
+        mock<TelephonyDisplayInfo>().also {
+            whenever(it.networkType).thenReturn(networkType)
+            whenever(it.overrideNetworkType).thenReturn(overrideNetworkType)
+        }
+
     inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
         val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
         assertThat(cbs.size).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 586bdc6..6e24941 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -685,7 +685,7 @@
         allowSwipeToDismiss: Boolean = false,
     ): ChipbarInfo {
         return ChipbarInfo(
-            TintedIcon(startIcon, tintAttr = null),
+            TintedIcon(startIcon, tint = null),
             text,
             endItem,
             vibrationEffect,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 8476d0d..bf54d42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.unfold.updates
 
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
 import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.core.util.Consumer
@@ -33,6 +36,7 @@
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
 import org.junit.Before
@@ -49,20 +53,19 @@
 @SmallTest
 class DeviceFoldStateProviderTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
+    @Mock private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
 
-    @Mock
-    private lateinit var handler: Handler
+    @Mock private lateinit var handler: Handler
 
-    @Mock
-    private lateinit var rotationChangeProvider: RotationChangeProvider
+    @Mock private lateinit var rotationChangeProvider: RotationChangeProvider
 
-    @Mock
-    private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
+    @Mock private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
 
-    @Captor
-    private lateinit var rotationListener: ArgumentCaptor<RotationListener>
+    @Mock private lateinit var resources: Resources
+
+    @Mock private lateinit var context: Context
+
+    @Captor private lateinit var rotationListener: ArgumentCaptor<RotationListener>
 
     private val foldProvider = TestFoldProvider()
     private val screenOnStatusProvider = TestScreenOnStatusProvider()
@@ -81,10 +84,13 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        val config = object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
-            override val halfFoldedTimeoutMillis: Int
-                get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
-        }
+        val config =
+            object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
+                override val halfFoldedTimeoutMillis: Int
+                    get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
+            }
+        whenever(context.resources).thenReturn(resources)
+        whenever(context.mainExecutor).thenReturn(mContext.mainExecutor)
 
         foldStateProvider =
             DeviceFoldStateProvider(
@@ -95,6 +101,7 @@
                 activityTypeProvider,
                 unfoldKeyguardVisibilityProvider,
                 rotationChangeProvider,
+                context,
                 context.mainExecutor,
                 handler
             )
@@ -112,7 +119,8 @@
                 override fun onUnfoldedScreenAvailable() {
                     unfoldedScreenAvailabilityUpdates.add(Unit)
                 }
-            })
+            }
+        )
         foldStateProvider.start()
 
         verify(rotationChangeProvider).addCallback(capture(rotationListener))
@@ -134,6 +142,7 @@
 
         // By default, we're on launcher.
         setupForegroundActivityType(isHomeActivity = true)
+        setIsLargeScreen(true)
     }
 
     @Test
@@ -181,7 +190,7 @@
         sendHingeAngleEvent(10)
 
         assertThat(foldUpdates)
-                .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
+            .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
         assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
     }
 
@@ -386,8 +395,10 @@
         setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
 
         sendHingeAngleEvent(
-                START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
-                        HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
+            START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
+                HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() -
+                1
+        )
 
         assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
     }
@@ -429,8 +440,10 @@
         setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
 
         sendHingeAngleEvent(
-                START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
-                        HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
+            START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
+                HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() -
+                1
+        )
 
         assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
     }
@@ -470,7 +483,7 @@
         sendHingeAngleEvent(130)
         sendHingeAngleEvent(120)
         assertThat(foldUpdates)
-                .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
+            .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
     }
 
     @Test
@@ -531,8 +544,8 @@
 
         rotationListener.value.onRotationChanged(1)
 
-        assertThat(foldUpdates).containsExactly(
-            FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
+        assertThat(foldUpdates)
+            .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
     }
 
     @Test
@@ -545,6 +558,45 @@
         assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
     }
 
+    @Test
+    fun onFolding_onSmallScreen_tansitionDoesNotStart() {
+        setIsLargeScreen(false)
+
+        setInitialHingeAngle(120)
+        sendHingeAngleEvent(110)
+        sendHingeAngleEvent(100)
+
+        assertThat(foldUpdates).isEmpty()
+    }
+
+    @Test
+    fun onFolding_onLargeScreen_tansitionStarts() {
+        setIsLargeScreen(true)
+
+        setInitialHingeAngle(120)
+        sendHingeAngleEvent(110)
+        sendHingeAngleEvent(100)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun onUnfold_onSmallScreen_emitsStartOpening() {
+        // the new display state might arrive later, so it shouldn't be used to decide to send the
+        // start opening event, but only for the closing.
+        setFoldState(folded = true)
+        setIsLargeScreen(false)
+        foldUpdates.clear()
+
+        setFoldState(folded = false)
+        screenOnStatusProvider.notifyScreenTurningOn()
+        sendHingeAngleEvent(10)
+        sendHingeAngleEvent(20)
+        screenOnStatusProvider.notifyScreenTurnedOn()
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
+    }
+
     private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
         whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
     }
@@ -566,6 +618,13 @@
         foldProvider.notifyFolded(folded)
     }
 
+    private fun setIsLargeScreen(isLargeScreen: Boolean) {
+        val smallestScreenWidth = if (isLargeScreen) { 601 } else { 10 }
+        val configuration = Configuration()
+        configuration.smallestScreenWidthDp = smallestScreenWidth
+        whenever(resources.configuration).thenReturn(configuration)
+    }
+
     private fun fireScreenOnEvent() {
         screenOnStatusProvider.notifyScreenTurnedOn()
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 926c6c5..c664c99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -47,7 +47,12 @@
     }
 
     @Override
-    public void setExternalIcon(String slot) {
+    public void setIconFromTile(String slot, StatusBarIcon icon) {
+
+    }
+
+    @Override
+    public void removeIconForTile(String slot) {
 
     }
 
@@ -57,11 +62,6 @@
     }
 
     @Override
-    public void setIcon(String slot, StatusBarIcon icon) {
-
-    }
-
-    @Override
     public void setWifiIcon(String slot, WifiIconState state) {
     }
 
@@ -98,10 +98,6 @@
     }
 
     @Override
-    public void removeAllIconsForExternalSlot(String slot) {
-    }
-
-    @Override
     public void setIconAccessibilityLiveRegion(String slot, int mode) {
     }
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
index 2044f05..380c1fc 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
@@ -53,4 +53,4 @@
     }
 }
 
-private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index d653fc7..a633a5e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,12 +15,14 @@
  */
 package com.android.systemui.unfold.updates
 
+import android.content.Context
 import android.os.Handler
 import android.os.Trace
 import android.util.Log
 import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
 import androidx.core.util.Consumer
+import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -45,6 +47,7 @@
     private val activityTypeProvider: CurrentActivityTypeProvider,
     private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider,
     private val rotationChangeProvider: RotationChangeProvider,
+    private val context: Context,
     @UnfoldMain private val mainExecutor: Executor,
     @UnfoldMain private val handler: Handler
 ) : FoldStateProvider {
@@ -119,7 +122,7 @@
                     "lastHingeAngle: $lastHingeAngle, " +
                     "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
             )
-            Trace.setCounter( "hinge_angle", angle.toLong())
+            Trace.setCounter("hinge_angle", angle.toLong())
         }
 
         val currentDirection =
@@ -136,6 +139,7 @@
         val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
         val eventNotAlreadyDispatched = lastFoldUpdate != transitionUpdate
         val screenAvailableEventSent = isUnfoldHandled
+        val isOnLargeScreen = isOnLargeScreen()
 
         if (
             angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle
@@ -144,7 +148,9 @@
                                   // angle range as closing threshold could overlap this range
                 screenAvailableEventSent && // do not send transition event if we are still in the
                                             // process of turning on the inner display
-                isClosingThresholdMet(angle) // hinge angle is below certain threshold.
+                isClosingThresholdMet(angle) && // hinge angle is below certain threshold.
+                isOnLargeScreen // Avoids sending closing event when on small screen.
+                                // Start event is sent regardless due to hall sensor.
         ) {
             notifyFoldUpdate(transitionUpdate, lastHingeAngle)
         }
@@ -233,7 +239,7 @@
     }
 
     private fun cancelAnimation(): Unit =
-            notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
+        notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
 
     private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
 
@@ -261,6 +267,11 @@
         }
     }
 
+    private fun isOnLargeScreen(): Boolean {
+      return context.resources.configuration.smallestScreenWidthDp >
+          INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
+    }
+
     /** While the screen is off or the device is folded, hinge angle updates are not needed. */
     private fun updateHingeAngleProviderState() {
         if (isScreenOn && !isFolded) {
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 3d24588..aafcd9d 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.graphics.Camera;
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -1855,6 +1854,7 @@
         ret.outputId.id = output.getId();
         ret.physicalCameraId = output.getPhysicalCameraId();
         ret.surfaceGroupId = output.getSurfaceGroupId();
+        ret.isMultiResolutionOutput = false;
         if (output instanceof SurfaceOutputConfigImpl) {
             SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output;
             ret.type = CameraOutputConfig.TYPE_SURFACE;
@@ -1874,6 +1874,7 @@
             ret.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER;
             ret.imageFormat = multiResReaderConfig.getImageFormat();
             ret.capacity = multiResReaderConfig.getMaxImages();
+            ret.isMultiResolutionOutput = true;
         } else {
             throw new IllegalStateException("Unknown output config type!");
         }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 4251581..1dc0942 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4893,10 +4893,6 @@
             if (intent.getClipData() == null) {
                 intent.setClipData(ClipData.newPlainText(null, null));
             }
-            intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
-                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
-                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION));
             final long bid = Binder.clearCallingIdentity();
             try {
                 PackageManager pm = mContext.getPackageManager();
@@ -4942,7 +4938,19 @@
             if (intent == null) {
                 return (simulateIntent == null);
             }
-            return intent.filterEquals(simulateIntent);
+            if (!intent.filterEquals(simulateIntent)) {
+                return false;
+            }
+
+            if (intent.getSelector() != simulateIntent.getSelector()) {
+                return false;
+            }
+
+            int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+            return (simulateIntent.getFlags() & prohibitedFlags) == 0;
         }
 
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 702526a..dc1ef7e 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -891,6 +891,7 @@
                     break;
                 case EnergyConsumerType.MOBILE_RADIO:
                     buckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO] = true;
+                    buckets[MeasuredEnergyStats.POWER_BUCKET_PHONE] = true;
                     break;
                 case EnergyConsumerType.DISPLAY:
                     buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ff18167..1584920 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1017,9 +1017,14 @@
 
         mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
-        final boolean headTrackingDefault = mContext.getResources().getBoolean(
+        final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+                "ro.audio.spatializer_binaural_enabled_default", true);
+        final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+                "ro.audio.spatializer_transaural_enabled_default", true);
+        final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
-        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, headTrackingDefault);
+        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+                binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
 
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 2b56666..a9e7d4b 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -171,13 +171,17 @@
     // initialization
     @SuppressWarnings("StaticAssignmentInConstructor")
     SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
-            boolean headTrackingEnabledByDefault) {
+            boolean binauralEnabledDefault,
+            boolean transauralEnabledDefault,
+            boolean headTrackingEnabledDefault) {
         mAudioService = mother;
         mASA = asa;
         // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
         // constructed here is the factory for SADeviceState, thus SADeviceState and its
         // private static field sHeadTrackingEnabledDefault should never be accessed directly.
-        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledByDefault;
+        SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
+        SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
+        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
     }
 
     synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
@@ -1539,10 +1543,12 @@
     }
 
     /*package*/ static final class SADeviceState {
+        private static boolean sBinauralEnabledDefault = true;
+        private static boolean sTransauralEnabledDefault = true;
         private static boolean sHeadTrackingEnabledDefault = false;
         final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
         final @NonNull String mDeviceAddress;
-        boolean mEnabled = true;               // by default, SA is enabled on any device
+        boolean mEnabled;
         boolean mHasHeadTracker = false;
         boolean mHeadTrackerEnabled;
         static final String SETTING_FIELD_SEPARATOR = ",";
@@ -1558,6 +1564,12 @@
         SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
             mDeviceType = deviceType;
             mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
+            final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+            mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+                    ? sBinauralEnabledDefault
+                    : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+                            ? sTransauralEnabledDefault
+                            : false;
             mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 1bbdc20..8c83be3 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -1230,13 +1230,12 @@
         }
 
         public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) {
-            if (defaultPeakRefreshRate == null) {
-                defaultPeakRefreshRate = (float) mContext.getResources().getInteger(
-                        R.integer.config_defaultPeakRefreshRate);
-            }
-
-            if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
-                synchronized (mLock) {
+            synchronized (mLock) {
+                if (defaultPeakRefreshRate == null) {
+                    setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
+                        /* attemptLoadingFromDeviceConfig= */ false);
+                    updateRefreshRateSettingLocked();
+                } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
                     mDefaultPeakRefreshRate = defaultPeakRefreshRate;
                     updateRefreshRateSettingLocked();
                 }
@@ -1869,11 +1868,20 @@
                 mLowDisplayBrightnessThresholds = displayThresholds;
                 mLowAmbientBrightnessThresholds = ambientThresholds;
             } else {
-                // Invalid or empty. Use device default.
-                mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
-                        R.array.config_brightnessThresholdsOfPeakRefreshRate);
-                mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
-                        R.array.config_ambientThresholdsOfPeakRefreshRate);
+                DisplayDeviceConfig displayDeviceConfig;
+                synchronized (mLock) {
+                    displayDeviceConfig = mDefaultDisplayDeviceConfig;
+                }
+                mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                    () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
+                    R.array.config_brightnessThresholdsOfPeakRefreshRate,
+                    displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                    () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
+                    R.array.config_ambientThresholdsOfPeakRefreshRate,
+                    displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
             }
             restartObserver();
         }
@@ -1883,24 +1891,41 @@
          * DeviceConfig properties.
          */
         public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) {
-            if (refreshRate != mRefreshRateInLowZone) {
+            if (refreshRate == -1) {
+                // Given there is no value available in DeviceConfig, lets not attempt loading it
+                // from there.
+                synchronized (mLock) {
+                    loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig,
+                        /* attemptLoadingFromDeviceConfig= */ false);
+                }
+                restartObserver();
+            } else if (refreshRate != mRefreshRateInLowZone) {
                 mRefreshRateInLowZone = refreshRate;
                 restartObserver();
             }
         }
 
-        public void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
+        private void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
                 int[] ambientThresholds) {
             if (displayThresholds != null && ambientThresholds != null
                     && displayThresholds.length == ambientThresholds.length) {
                 mHighDisplayBrightnessThresholds = displayThresholds;
                 mHighAmbientBrightnessThresholds = ambientThresholds;
             } else {
-                // Invalid or empty. Use device default.
-                mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray(
-                        R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
-                mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray(
-                        R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+                DisplayDeviceConfig displayDeviceConfig;
+                synchronized (mLock) {
+                    displayDeviceConfig = mDefaultDisplayDeviceConfig;
+                }
+                mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                    () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
+                    R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
+                    displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
+                    () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                    () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
+                    R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
+                    displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
             }
             restartObserver();
         }
@@ -1910,7 +1935,15 @@
          * DeviceConfig properties.
          */
         public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) {
-            if (refreshRate != mRefreshRateInHighZone) {
+            if (refreshRate == -1) {
+                // Given there is no value available in DeviceConfig, lets not attempt loading it
+                // from there.
+                synchronized (mLock) {
+                    loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig,
+                        /* attemptLoadingFromDeviceConfig= */ false);
+                }
+                restartObserver();
+            } else if (refreshRate != mRefreshRateInHighZone) {
                 mRefreshRateInHighZone = refreshRate;
                 restartObserver();
             }
@@ -2870,10 +2903,8 @@
                     new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
                     .sendToTarget();
 
-            if (refreshRateInLowZone != -1) {
-                mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
-                        0).sendToTarget();
-            }
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
+                0).sendToTarget();
 
             int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
             int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
@@ -2883,10 +2914,8 @@
                     new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
                     .sendToTarget();
 
-            if (refreshRateInHighZone != -1) {
-                mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone,
-                        0).sendToTarget();
-            }
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone,
+                0).sendToTarget();
 
             synchronized (mLock) {
                 final int refreshRateInHbmSunlight =
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d114139..4df34ca 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6751,7 +6751,8 @@
         }
 
         // Ensure MediaStyle has correct permissions for remote device extras
-        if (notification.isStyle(Notification.MediaStyle.class)) {
+        if (notification.isStyle(Notification.MediaStyle.class)
+                || notification.isStyle(Notification.DecoratedMediaCustomViewStyle.class)) {
             int hasMediaContentControlPermission = mPackageManager.checkPermission(
                     android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId);
             if (hasMediaContentControlPermission != PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index eedb4b0..be4fe09 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1508,7 +1508,9 @@
 
     private void interceptScreenshotChord(int source, long pressDelay) {
         mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
+        // arg2 is unused, but necessary to insure we call the correct method signature
+        // since the screenshot source is read from message.arg1
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source, 0),
                 pressDelay);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 01eacd0..2559699 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8591,7 +8591,7 @@
         resolvedBounds.set(containingBounds);
 
         final float letterboxAspectRatioOverride =
-                mLetterboxUiController.getFixedOrientationLetterboxAspectRatio();
+                mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig);
         final float desiredAspectRatio =
                 letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
                         ? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3404279..c0ed08d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -88,6 +88,10 @@
 public class DisplayRotation {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
 
+    // Delay in milliseconds when updating config due to folding events. This prevents
+    // config changes and unexpected jumps while folding the device to closed state.
+    private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800;
+
     private static class RotationAnimationPair {
         @AnimRes
         int mEnter;
@@ -1618,6 +1622,7 @@
         private boolean mInHalfFoldTransition = false;
         private final boolean mIsDisplayAlwaysSeparatingHinge;
         private final Set<Integer> mTabletopRotations;
+        private final Runnable mActivityBoundsUpdateCallback;
 
         FoldController() {
             mTabletopRotations = new ArraySet<>();
@@ -1652,6 +1657,26 @@
             }
             mIsDisplayAlwaysSeparatingHinge = mContext.getResources().getBoolean(
                     R.bool.config_isDisplayHingeAlwaysSeparating);
+
+            mActivityBoundsUpdateCallback = new Runnable() {
+                public void run() {
+                    if (mDeviceState == DeviceStateController.DeviceState.OPEN
+                            || mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
+                        synchronized (mLock) {
+                            final Task topFullscreenTask =
+                                    mDisplayContent.getTask(
+                                            t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+                            if (topFullscreenTask != null) {
+                                final ActivityRecord top =
+                                        topFullscreenTask.topRunningActivity();
+                                if (top != null) {
+                                    top.recomputeConfiguration();
+                                }
+                            }
+                        }
+                    }
+                }
+            };
         }
 
         boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
@@ -1723,14 +1748,9 @@
                         false /* forceRelayout */);
             }
             // Alert the activity of possible new bounds.
-            final Task topFullscreenTask =
-                    mDisplayContent.getTask(t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
-            if (topFullscreenTask != null) {
-                final ActivityRecord top = topFullscreenTask.topRunningActivity();
-                if (top != null) {
-                    top.recomputeConfiguration();
-                }
-            }
+            UiThread.getHandler().removeCallbacks(mActivityBoundsUpdateCallback);
+            UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback,
+                    FOLDING_RECOMPUTE_CONFIG_DELAY_MS);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 37cf5bc..ec04894 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -184,6 +184,10 @@
     // portrait device orientation.
     private boolean mIsVerticalReachabilityEnabled;
 
+    // Whether book mode automatic horizontal reachability positioning is allowed for letterboxed
+    // fullscreen apps in landscape device orientation.
+    private boolean mIsAutomaticReachabilityInBookModeEnabled;
+
     // Whether education is allowed for letterboxed fullscreen apps.
     private boolean mIsEducationEnabled;
 
@@ -277,6 +281,8 @@
                 R.bool.config_letterboxIsHorizontalReachabilityEnabled);
         mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsVerticalReachabilityEnabled);
+        mIsAutomaticReachabilityInBookModeEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsAutomaticReachabilityInBookModeEnabled);
         mDefaultPositionForHorizontalReachability =
                 readLetterboxHorizontalReachabilityPositionFromConfig(mContext, false);
         mDefaultPositionForVerticalReachability =
@@ -681,6 +687,14 @@
         return mIsVerticalReachabilityEnabled;
     }
 
+    /*
+     * Whether automatic horizontal reachability repositioning in book mode is allowed for
+     * letterboxed fullscreen apps in landscape device orientation.
+     */
+    boolean getIsAutomaticReachabilityInBookModeEnabled() {
+        return mIsAutomaticReachabilityInBookModeEnabled;
+    }
+
     /**
      * Overrides whether horizontal reachability repositioning is allowed for letterboxed fullscreen
      * apps in landscape device orientation.
@@ -698,6 +712,14 @@
     }
 
     /**
+     * Overrides whether automatic horizontal reachability repositioning in book mode is allowed for
+     * letterboxed fullscreen apps in landscape device orientation.
+     */
+    void setIsAutomaticReachabilityInBookModeEnabled(boolean enabled) {
+        mIsAutomaticReachabilityInBookModeEnabled = enabled;
+    }
+
+    /**
      * Resets whether horizontal reachability repositioning is allowed for letterboxed fullscreen
      * apps in landscape device orientation to
      * {@link R.bool.config_letterboxIsHorizontalReachabilityEnabled}.
@@ -717,6 +739,16 @@
                 R.bool.config_letterboxIsVerticalReachabilityEnabled);
     }
 
+    /**
+     * Resets whether automatic horizontal reachability repositioning in book mode is
+     * allowed for letterboxed fullscreen apps in landscape device orientation to
+     * {@link R.bool.config_letterboxIsAutomaticReachabilityInBookModeEnabled}.
+     */
+    void resetEnabledAutomaticReachabilityInBookMode() {
+        mIsAutomaticReachabilityInBookModeEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsAutomaticReachabilityInBookModeEnabled);
+    }
+
     /*
      * Gets default horizontal position of the letterboxed app window when horizontal reachability
      * is enabled.
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 21c9cdc..93c9a11 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -38,6 +38,10 @@
 import static android.content.pm.ActivityInfo.screenOrientationToString;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
@@ -188,7 +192,7 @@
     private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
 
     @Configuration.Orientation
-    private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+    private int mInheritedOrientation = ORIENTATION_UNDEFINED;
 
     // The app compat state for the opaque activity if any
     private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
@@ -797,13 +801,18 @@
     float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
         // Don't check resolved configuration because it may not be updated yet during
         // configuration change.
-        boolean bookMode = isDisplayFullScreenAndInPosture(
-                DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */);
+        boolean bookModeEnabled = isFullScreenAndBookModeEnabled();
         return isHorizontalReachabilityEnabled(parentConfiguration)
                 // Using the last global dynamic position to avoid "jumps" when moving
                 // between apps or activities.
-                ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookMode)
-                : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookMode);
+                ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled)
+                : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled);
+    }
+
+    private boolean isFullScreenAndBookModeEnabled() {
+        return isDisplayFullScreenAndInPosture(
+                DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */)
+                && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
     }
 
     float getVerticalPositionMultiplier(Configuration parentConfiguration) {
@@ -818,12 +827,14 @@
                 : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
     }
 
-    float getFixedOrientationLetterboxAspectRatio() {
+    float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
+        // Don't resize to split screen size when half folded if letterbox position is centered
         return isDisplayFullScreenAndSeparatingHinge()
-                ? getSplitScreenAspectRatio()
-                : mActivityRecord.shouldCreateCompatDisplayInsets()
-                    ? getDefaultMinAspectRatioForUnresizableApps()
-                    : getDefaultMinAspectRatio();
+                    && getHorizontalPositionMultiplier(parentConfiguration) != 0.5f
+                        ? getSplitScreenAspectRatio()
+                        : mActivityRecord.shouldCreateCompatDisplayInsets()
+                            ? getDefaultMinAspectRatioForUnresizableApps()
+                            : getDefaultMinAspectRatio();
     }
 
     private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -885,7 +896,8 @@
             return;
         }
 
-        boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge();
+        boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge()
+                && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
         int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
                 .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
         if (mLetterbox.getInnerFrame().left > x) {
@@ -1419,7 +1431,8 @@
         mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
                 mActivityRecord, firstOpaqueActivityBeneath,
                 (opaqueConfig, transparentConfig) -> {
-                    final Configuration mutatedConfiguration = new Configuration();
+                    final Configuration mutatedConfiguration =
+                            fromOriginalTranslucentConfig(transparentConfig);
                     final Rect parentBounds = parent.getWindowConfiguration().getBounds();
                     final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
                     final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
@@ -1507,6 +1520,22 @@
                 true /* traverseTopToBottom */));
     }
 
+    // When overriding translucent activities configuration we need to keep some of the
+    // original properties
+    private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) {
+        final Configuration configuration = new Configuration(translucentConfig);
+        // The values for the following properties will be defined during the configuration
+        // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the
+        // properties inherited from the first not finishing opaque activity beneath.
+        configuration.orientation = ORIENTATION_UNDEFINED;
+        configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
+        configuration.screenHeightDp =
+                configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
+        configuration.smallestScreenWidthDp =
+                configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+        return configuration;
+    }
+
     private void inheritConfiguration(ActivityRecord firstOpaque) {
         // To avoid wrong behaviour, we're not forcing a specific aspect ratio to activities
         // which are not already providing one (e.g. permission dialogs) and presumably also
@@ -1526,7 +1555,7 @@
         mLetterboxConfigListener = null;
         mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
         mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
-        mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+        mInheritedOrientation = ORIENTATION_UNDEFINED;
         mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
         mInheritedCompatDisplayInsets = null;
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3461bc2..18d6dea 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3448,6 +3448,8 @@
                 && info.pictureInPictureParams.isLaunchIntoPip()
                 && top.getLastParentBeforePip() != null)
                         ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
+        info.lastParentTaskIdBeforePip = top != null && top.getLastParentBeforePip() != null
+                ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
         info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
         info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a06d84c..adf5310 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -942,6 +942,10 @@
                     runSetBooleanFlag(pw, mLetterboxConfiguration
                             ::setIsVerticalReachabilityEnabled);
                     break;
+                case "--isAutomaticReachabilityInBookModeEnabled":
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsAutomaticReachabilityInBookModeEnabled);
+                    break;
                 case "--defaultPositionForHorizontalReachability":
                     runSetLetterboxDefaultPositionForHorizontalReachability(pw);
                     break;
@@ -1144,6 +1148,7 @@
             mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
             mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
             mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
+            mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
             mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
             mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
             mLetterboxConfiguration.resetIsEducationEnabled();
@@ -1179,6 +1184,8 @@
                     + mLetterboxConfiguration.getIsHorizontalReachabilityEnabled());
             pw.println("Is vertical reachability enabled: "
                     + mLetterboxConfiguration.getIsVerticalReachabilityEnabled());
+            pw.println("Is automatic reachability in book mode enabled: "
+                    + mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled());
             pw.println("Default position for horizontal reachability: "
                     + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
                             mLetterboxConfiguration.getDefaultPositionForHorizontalReachability()));
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 41e0fd7..2d849b3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2049,16 +2049,19 @@
 
     /**
      * Like isOnScreen(), but we don't return true if the window is part
-     * of a transition that has not yet been started.
+     * of a transition but has not yet started animating.
      */
     boolean isReadyForDisplay() {
-        if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) {
+        if (!mHasSurface || mDestroying || !isVisibleByPolicy()) {
+            return false;
+        }
+        if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()
+                && !isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
             return false;
         }
         final boolean parentAndClientVisible = !isParentWindowHidden()
                 && mViewVisibility == View.VISIBLE && mToken.isVisible();
-        return mHasSurface && isVisibleByPolicy() && !mDestroying
-                && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
+        return parentAndClientVisible || isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_ALL);
     }
 
     boolean isFullyTransparent() {
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 30ec163..881d1b3 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -18,6 +18,7 @@
 
 import static android.database.sqlite.SQLiteDatabase.deleteDatabase;
 
+import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -708,6 +709,41 @@
     }
 
     @SmallTest
+    public void testStartAddAccountSessionWhereAuthenticatorReturnsIntentWithProhibitedFlags()
+            throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        Bundle options = createOptionsWithAccountName(
+                AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE);
+        int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+        options.putInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, prohibitedFlags);
+
+        mAms.startAddAccountSession(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                options); // optionsIn
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), contains("invalid intent"));
+    }
+
+    @SmallTest
     public void testStartAddAccountSessionError() throws Exception {
         unlockSystemUser();
         Bundle options = createOptionsWithAccountName(
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
index 73f30d9..b98a6a8 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
@@ -17,9 +17,6 @@
 
 import android.accounts.Account;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Constants shared between test AccountAuthenticators and AccountManagerServiceTest.
  */
@@ -31,6 +28,8 @@
             "account_manager_service_test:account_status_token_key";
     public static final String KEY_ACCOUNT_PASSWORD =
             "account_manager_service_test:account_password_key";
+    public static final String KEY_INTENT_FLAGS =
+            "account_manager_service_test:intent_flags_key";
     public static final String KEY_OPTIONS_BUNDLE =
             "account_manager_service_test:option_bundle_key";
     public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com";
diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
index 8106364..924443e 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
@@ -24,8 +24,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 
-import com.android.frameworks.servicestests.R;
-
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -270,11 +268,13 @@
         String accountName = null;
         Bundle sessionBundle = null;
         String password = null;
+        int intentFlags = 0;
         if (options != null) {
             accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME);
             sessionBundle = options.getBundle(
                     AccountManagerServiceTestFixtures.KEY_ACCOUNT_SESSION_BUNDLE);
             password = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_PASSWORD);
+            intentFlags = options.getInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, 0);
         }
 
         Bundle result = new Bundle();
@@ -302,6 +302,7 @@
             intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
                     eventualActivityResultData);
             intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+            intent.setFlags(intentFlags);
 
             result.putParcelable(AccountManager.KEY_INTENT, intent);
         } else {
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index 428eaff..dea31d7 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -56,7 +56,9 @@
         mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
 
         mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
-                false /*headTrackingEnabledByDefault*/);
+                true /*binauralEnabledDefault*/,
+                true /*transauralEnabledDefault*/,
+                false /*headTrackingEnabledDefault*/);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index ff37564..58f3db9 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1905,7 +1905,7 @@
         // We don't expect any interaction with DeviceConfig when the director is initialized
         // because we explicitly avoid doing this as this can lead to a latency spike in the
         // startup of DisplayManagerService
-        // Verify all the loaded values are from DisplayDeviceConfig
+        // Verify all the loaded values are from config.xml
         assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
         assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
                 0.0);
@@ -1937,6 +1937,7 @@
         when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75);
         director.defaultDisplayDeviceUpdated(displayDeviceConfig);
 
+        // Verify the new values are from the freshly loaded DisplayDeviceConfig.
         assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
         assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
                 0.0);
@@ -1966,6 +1967,7 @@
         config.setRefreshRateInHbmSunlight(80);
         director.defaultDisplayDeviceUpdated(displayDeviceConfig);
 
+        // Verify the values are loaded from the DeviceConfig.
         assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
         assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
                 0.0);
@@ -1981,6 +1983,35 @@
                 new int[]{20});
         assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80);
+
+        // Reset the DeviceConfig
+        config.setDefaultPeakRefreshRate(null);
+        config.setRefreshRateInHighZone(null);
+        config.setRefreshRateInLowZone(null);
+        config.setLowAmbientBrightnessThresholds(new int[]{});
+        config.setLowDisplayBrightnessThresholds(new int[]{});
+        config.setHighDisplayBrightnessThresholds(new int[]{});
+        config.setHighAmbientBrightnessThresholds(new int[]{});
+        config.setRefreshRateInHbmHdr(null);
+        config.setRefreshRateInHbmSunlight(null);
+        waitForIdleSync();
+
+        // verify the new values now fallback to DisplayDeviceConfig
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
+                0.0);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+                new int[]{210});
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+                new int[]{2100});
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+                new int[]{25});
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+                new int[]{30});
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
     }
 
     @Test
@@ -2140,18 +2171,18 @@
             super.addOnPropertiesChangedListener(namespace, executor, listener);
         }
 
-        void setRefreshRateInLowZone(int fps) {
+        void setRefreshRateInLowZone(Integer fps) {
             putPropertyAndNotify(
                     DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE,
                     String.valueOf(fps));
         }
 
-        void setRefreshRateInHbmSunlight(int fps) {
+        void setRefreshRateInHbmSunlight(Integer fps) {
             putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps));
         }
 
-        void setRefreshRateInHbmHdr(int fps) {
+        void setRefreshRateInHbmHdr(Integer fps) {
             putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
         }
@@ -2187,13 +2218,13 @@
                     thresholds);
         }
 
-        void setRefreshRateInHighZone(int fps) {
+        void setRefreshRateInHighZone(Integer fps) {
             putPropertyAndNotify(
                     DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE,
                     String.valueOf(fps));
         }
 
-        void setDefaultPeakRefreshRate(int fps) {
+        void setDefaultPeakRefreshRate(Integer fps) {
             putPropertyAndNotify(
                     DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
                     String.valueOf(fps));
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index b921838..4c0361d 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -263,7 +263,7 @@
                 + instrumentation.getContext().getUserId() + " " + RoleManager.ROLE_HOME + " "
                 + packageName + " 0");
         waitUntil("Failed to get shortcut access",
-                () -> hasShortcutAccess(instrumentation, packageName), 20);
+                () -> hasShortcutAccess(instrumentation, packageName), 60);
     }
 
     public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 99a361c..874846d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -4359,6 +4359,43 @@
 
         assertFalse(posted.getNotification().extras
                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+        assertFalse(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_MEDIA_REMOTE_ICON));
+        assertFalse(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_MEDIA_REMOTE_INTENT));
+    }
+
+    @Test
+    public void testCustomMediaStyleRemote_noPermission() throws RemoteException {
+        String deviceName = "device";
+        when(mPackageManager.checkPermission(
+                eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
+                .thenReturn(PERMISSION_DENIED);
+        Notification.DecoratedMediaCustomViewStyle style =
+                new Notification.DecoratedMediaCustomViewStyle();
+        style.setRemotePlaybackInfo(deviceName, 0, null);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setStyle(style);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testCustomMediaStyleRemoteNoPermission", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        NotificationRecord posted = mService.findNotificationLocked(
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+        assertFalse(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
+        assertFalse(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_MEDIA_REMOTE_ICON));
+        assertFalse(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_MEDIA_REMOTE_INTENT));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index c40f9cf..d266da1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -349,6 +350,33 @@
     }
 
     @Test
+    public void testApplyStrategyToTranslucentActivitiesRetainsWindowConfigurationProperties() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        WindowConfiguration translucentWinConf = translucentActivity.getWindowConfiguration();
+        translucentActivity.setActivityType(ACTIVITY_TYPE_STANDARD);
+        translucentActivity.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        translucentActivity.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        translucentActivity.setAlwaysOnTop(true);
+
+        mTask.addChild(translucentActivity);
+
+        // We check the WIndowConfiguration properties
+        translucentWinConf = translucentActivity.getWindowConfiguration();
+        assertEquals(ACTIVITY_TYPE_STANDARD, translucentActivity.getActivityType());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getWindowingMode());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getDisplayWindowingMode());
+        assertTrue(translucentWinConf.isAlwaysOnTop());
+    }
+
+    @Test
     public void testApplyStrategyToMultipleTranslucentActivities() {
         mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
         setUpDisplaySizeWithApp(2000, 1000);
@@ -3669,7 +3697,6 @@
 
     @Test
     public void testUpdateResolvedBoundsVerticalPosition_tabletop() {
-
         // Set up a display in portrait with a fixed-orientation LANDSCAPE app
         setUpDisplaySizeWithApp(1400, 2800);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -3691,16 +3718,15 @@
         setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
 
         assertEquals(letterboxNoFold, mActivity.getBounds());
-
     }
 
     @Test
-    public void testUpdateResolvedBoundsHorizontalPosition_book() {
-
+    public void testUpdateResolvedBoundsHorizontalPosition_bookModeEnabled() {
         // Set up a display in landscape with a fixed-orientation PORTRAIT app
         setUpDisplaySizeWithApp(2800, 1400);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+        mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true);
+        mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
                 1.0f /*letterboxVerticalPositionMultiplier*/);
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
@@ -3718,7 +3744,28 @@
         setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
 
         assertEquals(letterboxNoFold, mActivity.getBounds());
+    }
 
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_bookModeDisabled_centered() {
+        // Set up a display in landscape with a fixed-orientation PORTRAIT app
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
+        prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT);
+
+        Rect letterboxNoFold = new Rect(1000, 0, 1800, 1400);
+        assertEquals(letterboxNoFold, mActivity.getBounds());
+
+        // Make the activity full-screen
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        // Stay centered and bounds don't change
+        setFoldablePosture(true /* isHalfFolded */, false /* isTabletop */);
+        assertEquals(letterboxNoFold, mActivity.getBounds());
+
+        setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
+        assertEquals(letterboxNoFold, mActivity.getBounds());
     }
 
     private void setFoldablePosture(ActivityRecord activity, boolean isHalfFolded,