Merge "Temporary disable starting surface for back predict target." into udc-qpr-dev
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 55133ae..9b1132a 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -46,7 +46,14 @@
      * <p> See details on usage of {@code Action} for various actionable entries in
      * {@link BeginCreateCredentialResponse} and {@link BeginGetCredentialResponse}.
      *
-     * @param slice the display content to be displayed on the UI, along with this action
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.Action} class populated as slice items
+     *              against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      */
     public Action(@NonNull Slice slice) {
         Objects.requireNonNull(slice, "slice must not be null");
diff --git a/core/java/android/service/credentials/CreateEntry.java b/core/java/android/service/credentials/CreateEntry.java
index 6a9f09f..2495c7d 100644
--- a/core/java/android/service/credentials/CreateEntry.java
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -66,7 +66,14 @@
     /**
      * Constructs a CreateEntry to be displayed on the UI.
      *
-     * @param slice the display content to be displayed on the UI, along with this entry
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.CreateEntry} class populated as slice items
+     *              against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      */
     public CreateEntry(
             @NonNull Slice slice) {
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 512d833..53094e8 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -69,8 +69,14 @@
      *                                   receive the complete corresponding
      *                                   {@link GetCredentialRequest}.
      * @param type the type of the credential for which this credential entry is being created
-     * @param slice the slice containing the metadata to be shown on the UI. Must be
-     *              constructed through the androidx.credentials jetpack library.
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.CredentialEntry} class populated as slice
+     *              items against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      *
      * @throws IllegalArgumentException If {@code beginGetCredentialOptionId} or {@code type}
      * is null, or empty
diff --git a/core/java/android/service/credentials/RemoteEntry.java b/core/java/android/service/credentials/RemoteEntry.java
index 5b3218a..5fd9925 100644
--- a/core/java/android/service/credentials/RemoteEntry.java
+++ b/core/java/android/service/credentials/RemoteEntry.java
@@ -73,7 +73,14 @@
     /**
      * Constructs a RemoteEntry to be displayed on the UI.
      *
-     * @param slice the display content to be displayed on the UI, along with this entry
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.RemoteEntry} class populated as slice items
+     *              against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      */
     public RemoteEntry(
             @NonNull Slice slice) {
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index fff778c..755113b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -33,6 +33,7 @@
 import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
 import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
 import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -142,6 +143,7 @@
     private ProcessState mCommonProcess;
     private int mCurCombinedState = STATE_NOTHING;
     private long mStartTime;
+    private int mStateBeforeFrozen = STATE_NOTHING;
 
     private int mLastPssState = STATE_NOTHING;
     private long mLastPssTime;
@@ -423,6 +425,27 @@
     }
 
     /**
+     * Used to notify that this process was frozen.
+     */
+    public void onProcessFrozen(long now,
+            ArrayMap<String, ProcessStateHolder> pkgList) {
+        mStateBeforeFrozen = mCurCombinedState % STATE_COUNT;
+        int currentMemFactor = mCurCombinedState / STATE_COUNT;
+        int combinedState = STATE_FROZEN + (currentMemFactor * STATE_COUNT);
+        setCombinedState(combinedState, now, pkgList);
+    }
+
+    /**
+     * Used to notify that this process was unfrozen.
+     */
+    public void onProcessUnfrozen(long now,
+            ArrayMap<String, ProcessStateHolder> pkgList) {
+        int currentMemFactor = mCurCombinedState / STATE_COUNT;
+        int combinedState = mStateBeforeFrozen + (currentMemFactor * STATE_COUNT);
+        setCombinedState(combinedState, now, pkgList);
+    }
+
+    /**
      * Update the current state of the given list of processes.
      *
      * @param state Current ActivityManager.PROCESS_STATE_*
@@ -434,13 +457,20 @@
             ArrayMap<String, ProcessStateHolder> pkgList) {
         if (state < 0) {
             state = mNumStartedServices > 0
-                    ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
+                    ? (STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT)) : STATE_NOTHING;
         } else {
-            state = PROCESS_STATE_TO_STATE[state] + (memFactor*STATE_COUNT);
+            state = PROCESS_STATE_TO_STATE[state] + (memFactor * STATE_COUNT);
         }
+        setCombinedState(state, now, pkgList);
+    }
 
+    /**
+     * Sets combined state on the corresponding ProcessState objects.
+     */
+    void setCombinedState(int state, long now,
+            ArrayMap<String, ProcessStateHolder> pkgList) {
         // First update the common process.
-        mCommonProcess.setCombinedState(state, now);
+        mCommonProcess.setCombinedStateIdv(state, now);
 
         // If the common process is not multi-package, there is nothing else to do.
         if (!mCommonProcess.mMultiPackage) {
@@ -449,12 +479,15 @@
 
         if (pkgList != null) {
             for (int ip=pkgList.size()-1; ip>=0; ip--) {
-                pullFixedProc(pkgList, ip).setCombinedState(state, now);
+                pullFixedProc(pkgList, ip).setCombinedStateIdv(state, now);
             }
         }
     }
 
-    public void setCombinedState(int state, long now) {
+    /**
+     * Sets the combined state for this individual ProcessState object.
+     */
+    void setCombinedStateIdv(int state, long now) {
         ensureNotDead();
         if (!mDead && (mCurCombinedState != state)) {
             //Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
@@ -545,7 +578,7 @@
         }
         mNumStartedServices++;
         if (mNumStartedServices == 1 && mCurCombinedState == STATE_NOTHING) {
-            setCombinedState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
+            setCombinedStateIdv(STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT), now);
         }
     }
 
@@ -561,7 +594,7 @@
         }
         mNumStartedServices--;
         if (mNumStartedServices == 0 && (mCurCombinedState %STATE_COUNT) == STATE_SERVICE_RESTARTING) {
-            setCombinedState(STATE_NOTHING, now);
+            setCombinedStateIdv(STATE_NOTHING, now);
         } else if (mNumStartedServices < 0) {
             Slog.wtfStack(TAG, "Proc started services underrun: pkg="
                     + mPackage + " uid=" + mUid + " name=" + mName);
@@ -1588,7 +1621,9 @@
                 case STATE_CACHED:
                     cachedMs += duration;
                     break;
-                    // TODO (b/261910877) Add support for tracking frozenMs.
+                case STATE_FROZEN:
+                    frozenMs += duration;
+                    break;
             }
         }
         statsEventOutput.write(
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 735e590..f0f39d1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3537,11 +3537,11 @@
         <attr name="preferKeepClear" format="boolean" />
 
         <!-- <p>Whether or not the auto handwriting initiation is enabled in this View.
-             <p>For a view with active {@link android.view.inputmethod.InputConnection},
-             if auto handwriting initiation is enabled stylus movement within its view boundary
+             <p>For a view with an active {@link android.view.inputmethod.InputConnection},
+             if auto handwriting initiation is enabled, stylus movement within its view boundary
              will automatically trigger the handwriting mode.
-             <p>This is true by default.
-             See {@link android.view.View#setAutoHandwritingEnabled}. -->
+             See {@link android.view.View#setAutoHandwritingEnabled}.
+             <p>The default value of this flag is configurable by the device manufacturer. -->
         <attr name="autoHandwritingEnabled" format="boolean" />
 
         <!-- <p>The amount of offset that is applied to the left edge of the view's stylus
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a7625cb..4184e79 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6567,6 +6567,9 @@
          Off by default, since OEMs may have had a similar feature on their devices. -->
     <bool name="config_repairModeSupported">false</bool>
 
+    <!-- Whether unlocking and waking a device are sequenced -->
+    <bool name="config_orderUnlockAndWake">false</bool>
+
     <!-- Enables or disables the "Share" action item shown in the context menu that appears upon
         long-pressing on selected text. Enabled by default. -->
     <bool name="config_textShareSupported">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 11ba6ab..81fbc38 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5160,4 +5160,7 @@
 
   <java-symbol type="drawable" name="focus_event_pressed_key_background" />
   <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
+
+  <!-- Whether we order unlocking and waking -->
+  <java-symbol type="bool" name="config_orderUnlockAndWake" />
 </resources>
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 6189914..fe30d81 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
@@ -16,8 +16,6 @@
 
 package com.android.internal.app.procstats;
 
-import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
-
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
@@ -108,7 +106,8 @@
         ProcessState processState =
                 processStats.getProcessStateLocked(
                         APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
-        processState.setCombinedState(STATE_TOP, NOW_MS);
+        processState.setState(ActivityManager.PROCESS_STATE_TOP, ProcessStats.ADJ_MEM_FACTOR_NORMAL,
+                NOW_MS, /* pkgList */ null);
         processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
         processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
         verify(mStatsEventOutput)
@@ -158,6 +157,38 @@
     }
 
     @SmallTest
+    public void testDumpFrozenDuration() 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.setState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+                ProcessStats.ADJ_MEM_FACTOR_NORMAL, NOW_MS, /* pkgList */ null);
+        processState.onProcessFrozen(NOW_MS   + 1 * TimeUnit.SECONDS.toMillis(DURATION_SECS),
+            /* pkgList */ null);
+        processState.onProcessUnfrozen(NOW_MS + 2 * TimeUnit.SECONDS.toMillis(DURATION_SECS),
+            /* pkgList */ null);
+        processState.commitStateTime(NOW_MS   + 3 * TimeUnit.SECONDS.toMillis(DURATION_SECS));
+        processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+        verify(mStatsEventOutput)
+                .write(
+                        eq(FrameworkStatsLog.PROCESS_STATE),
+                        eq(APP_1_UID),
+                        eq(APP_1_PROCESS_NAME),
+                        anyInt(),
+                        anyInt(),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(2 * DURATION_SECS),  // bound_fgs
+                        eq(0),
+                        eq(0),
+                        eq(DURATION_SECS),  // frozen
+                        eq(0));
+    }
+
+    @SmallTest
     public void testDumpProcessAssociation() throws Exception {
         ProcessStats processStats = new ProcessStats();
         AssociationState associationState =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
index fd000ee..a8743fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
@@ -21,7 +21,6 @@
 import android.graphics.PointF
 import android.util.Size
 import com.android.wm.shell.R
-import com.android.wm.shell.pip.PipDisplayLayoutState
 
 class LegacySizeSpecSource(
         private val context: Context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
index 2d34035..133242d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -24,9 +24,6 @@
 import android.view.Gravity;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 
 import java.util.Set;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
index c563068..18c7bdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -21,7 +21,6 @@
 import android.os.SystemProperties
 import android.util.Size
 import com.android.wm.shell.R
-import com.android.wm.shell.pip.PipDisplayLayoutState
 import java.io.PrintWriter
 
 class PhoneSizeSpecSource(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index 4fef672..a9f687f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,8 +28,6 @@
 import android.view.Gravity;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import java.io.PrintWriter;
 
@@ -202,7 +200,8 @@
      *
      * @return {@code false} if the given source is too small to use for the entering animation.
      */
-    static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+    public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint,
+            Rect destinationBounds) {
         return sourceRectHint != null
                 && sourceRectHint.width() > destinationBounds.width()
                 && sourceRectHint.height() > destinationBounds.height();
@@ -224,7 +223,7 @@
     }
 
     /**
-     * @return whether the given {@param aspectRatio} is valid.
+     * @return whether the given aspectRatio is valid.
      */
     public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
         return Float.compare(mMinAspectRatio, aspectRatio) <= 0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 279ffc5..3b32b6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -36,7 +36,6 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.io.PrintWriter;
@@ -314,8 +313,11 @@
         return mPipDisplayLayoutState.getDisplayLayout();
     }
 
+    /**
+     * Clears the PiP re-entry state.
+     */
     @VisibleForTesting
-    void clearReentryState() {
+    public void clearReentryState() {
         mPipReentryState = null;
     }
 
@@ -400,11 +402,18 @@
         mNamedUnrestrictedKeepClearAreas.remove(name);
     }
 
+
+    /**
+     * @return restricted keep clear areas.
+     */
     @NonNull
     public Set<Rect> getRestrictedKeepClearAreas() {
         return mRestrictedKeepClearAreas;
     }
 
+    /**
+     * @return unrestricted keep clear areas.
+     */
     @NonNull
     public Set<Rect> getUnrestrictedKeepClearAreas() {
         if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
@@ -561,7 +570,11 @@
         }
     }
 
-    static final class PipReentryState {
+    /**
+     * Represents the state of pip to potentially restore upon reentry.
+     */
+    @VisibleForTesting
+    public static final class PipReentryState {
         private static final String TAG = PipReentryState.class.getSimpleName();
 
         private final @Nullable Size mSize;
@@ -573,11 +586,11 @@
         }
 
         @Nullable
-        Size getSize() {
+        public Size getSize() {
             return mSize;
         }
 
-        float getSnapFraction() {
+        public float getSnapFraction() {
             return mSnapFraction;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
index 4aa260b..ed42117 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
 
 import static com.android.wm.shell.common.pip.PipUtils.dpToPx;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
index 5045cf9..954233c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
 
 import android.graphics.Rect;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
index 23153be72..02b3a88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
 
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -35,7 +35,7 @@
     private final PointF mTmpLastCentroid = new PointF();
 
     /**
-     * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in
+     * Updates resizeBoundsOut with the new bounds of the PIP, and returns the angle in
      * degrees that the PIP should be rotated.
      */
     public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
similarity index 84%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
index dd30137..007052e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
 
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
 
 import android.graphics.Rect;
 
@@ -39,14 +39,14 @@
     }
 
     /**
-     * @return returns a fraction that describes where along the {@param movementBounds} the
-     *         {@param stackBounds} are. If the {@param stackBounds} are not currently on the
-     *         {@param movementBounds} exactly, then they will be snapped to the movement bounds.
+     * @return returns a fraction that describes where along the movementBounds the
+     *         stackBounds are. If the stackBounds are not currently on the
+     *         movementBounds exactly, then they will be snapped to the movement bounds.
      *         stashType dictates whether the PiP is stashed (off-screen) or not. If
      *         that's the case, we will have to do some math to calculate the snap fraction
      *         correctly.
      *
-     *         The fraction is defined in a clockwise fashion against the {@param movementBounds}:
+     *         The fraction is defined in a clockwise fashion against the movementBounds:
      *
      *            0   1
      *          4 +---+ 1
@@ -58,10 +58,10 @@
             @PipBoundsState.StashType int stashType) {
         final Rect tmpBounds = new Rect();
         snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds, stashType);
-        final float widthFraction = (float) (tmpBounds.left - movementBounds.left) /
-                movementBounds.width();
-        final float heightFraction = (float) (tmpBounds.top - movementBounds.top) /
-                movementBounds.height();
+        final float widthFraction = (float) (tmpBounds.left - movementBounds.left)
+                / movementBounds.width();
+        final float heightFraction = (float) (tmpBounds.top - movementBounds.top)
+                / movementBounds.height();
         if (tmpBounds.top == movementBounds.top) {
             return widthFraction;
         } else if (tmpBounds.left == movementBounds.right) {
@@ -74,10 +74,10 @@
     }
 
     /**
-     * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction.
+     * Moves the stackBounds along the movementBounds to the given snap fraction.
      * See {@link #getSnapFraction(Rect, Rect)}.
      *
-     * The fraction is define in a clockwise fashion against the {@param movementBounds}:
+     * The fraction is define in a clockwise fashion against the movementBounds:
      *
      *    0   1
      *  4 +---+ 1
@@ -122,11 +122,11 @@
     }
 
     /**
-     * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
-     * the new bounds out to {@param boundsOut}.
+     * Snaps the stackBounds to the closest edge of the movementBounds and writes
+     * the new bounds out to boundsOut.
      */
     @VisibleForTesting
-    void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
+    public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
             @PipBoundsState.StashType int stashType) {
         int leftEdge = stackBounds.left;
         if (stashType == STASH_TYPE_LEFT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index b454807..45869e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -58,8 +58,15 @@
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.compatui.CompatUIConfiguration;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -343,6 +350,42 @@
         return new PipMediaController(context, mainHandler);
     }
 
+    @WMSingleton
+    @Provides
+    static SizeSpecSource provideSizeSpecSource(Context context,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipBoundsState providePipBoundsState(Context context,
+            SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
+    }
+
+
+    @WMSingleton
+    @Provides
+    static PipSnapAlgorithm providePipSnapAlgorithm() {
+        return new PipSnapAlgorithm();
+    }
+
+    @WMSingleton
+    @Provides
+    static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
+        return new PhonePipKeepClearAlgorithm(context);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
+            PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+        return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
+                pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
+    }
 
     //
     // Bubbles (optional feature)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 4e92ca1..ba882c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -30,9 +30,13 @@
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
@@ -41,17 +45,12 @@
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransition;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
@@ -119,35 +118,6 @@
         }
     }
 
-    @WMSingleton
-    @Provides
-    static PipBoundsState providePipBoundsState(Context context,
-            SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSnapAlgorithm providePipSnapAlgorithm() {
-        return new PipSnapAlgorithm();
-    }
-
-    @WMSingleton
-    @Provides
-    static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
-        return new PhonePipKeepClearAlgorithm(context);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
-            PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
-            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
-            PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
-        return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
-                pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
-    }
-
     // Handler is used by Icon.loadDrawableAsync
     @WMSingleton
     @Provides
@@ -213,13 +183,6 @@
 
     @WMSingleton
     @Provides
-    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
-            pipSurfaceTransactionHelper) {
-        return new PipAnimationController(pipSurfaceTransactionHelper);
-    }
-
-    @WMSingleton
-    @Provides
     static PipTransition providePipTransition(Context context,
             ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
             PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -235,13 +198,6 @@
 
     @WMSingleton
     @Provides
-    static SizeSpecSource provideSizeSpecSource(Context context,
-            PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
             PipTouchHandler pipTouchHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
index c4ca501..b42372b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 
 import dagger.Module;
@@ -35,4 +36,11 @@
     static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
         return new PipSurfaceTransactionHelper(context);
     }
+
+    @WMSingleton
+    @Provides
+    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+            pipSurfaceTransactionHelper) {
+        return new PipAnimationController(pipSurfaceTransactionHelper);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8dec4ea..af97cf6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -16,11 +16,16 @@
 
 package com.android.wm.shell.dagger.pip;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip2.PipTransition;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 import dagger.Module;
 import dagger.Provides;
@@ -33,8 +38,12 @@
 public abstract class Pip2Module {
     @WMSingleton
     @Provides
-    @Nullable
-    static PipTransition providePipTransition() {
-        return null;
+    static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            @NonNull Transitions transitions,
+            PipBoundsState pipBoundsState,
+            PipBoundsAlgorithm pipBoundsAlgorithm) {
+        return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
+                pipBoundsAlgorithm);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index 9c9364e..570f0a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.dagger.pip;
 
-import android.annotation.Nullable;
-
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -38,8 +36,8 @@
     @Provides
     static PipTransitionController providePipTransitionController(
             com.android.wm.shell.pip.PipTransition legacyPipTransition,
-            @Nullable com.android.wm.shell.pip2.PipTransition newPipTransition) {
-        if (PipUtils.isPip2ExperimentEnabled() && newPipTransition != null) {
+            com.android.wm.shell.pip2.PipTransition newPipTransition) {
+        if (PipUtils.isPip2ExperimentEnabled()) {
             return newPipTransition;
         } else {
             return legacyPipTransition;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index a6ff9ec..a9675f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -30,16 +30,15 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.pip.LegacySizeSpecSource;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -131,15 +130,9 @@
 
     @WMSingleton
     @Provides
-    static PipSnapAlgorithm providePipSnapAlgorithm() {
-        return new PipSnapAlgorithm();
-    }
-
-    @WMSingleton
-    @Provides
     static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
             TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
-            PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+            PipDisplayLayoutState pipDisplayLayoutState, LegacySizeSpecSource sizeSpecSource) {
         return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
                 pipDisplayLayoutState, sizeSpecSource);
     }
@@ -147,13 +140,13 @@
     @WMSingleton
     @Provides
     static TvPipBoundsState provideTvPipBoundsState(Context context,
-            SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+            LegacySizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
         return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
     }
 
     @WMSingleton
     @Provides
-    static SizeSpecSource provideSizeSpecSource(Context context,
+    static LegacySizeSpecSource provideSizeSpecSource(Context context,
             PipDisplayLayoutState pipDisplayLayoutState) {
         return new LegacySizeSpecSource(context, pipDisplayLayoutState);
     }
@@ -200,13 +193,6 @@
 
     @WMSingleton
     @Provides
-    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
-            pipSurfaceTransactionHelper) {
-        return new PipAnimationController(pipSurfaceTransactionHelper);
-    }
-
-    @WMSingleton
-    @Provides
     static PipTransitionState providePipTransitionState() {
         return new PipTransitionState();
     }
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 ed9ff1c..9e8f9c6 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
@@ -82,6 +82,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 83e03dc..e3922d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -64,6 +64,9 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -89,6 +92,7 @@
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Optional<SplitScreenController> mSplitScreenOptional;
+    private final PipAnimationController mPipAnimationController;
     private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
     private Transitions.TransitionFinishCallback mFinishCallback;
     private SurfaceControl.Transaction mFinishTransaction;
@@ -137,10 +141,11 @@
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<SplitScreenController> splitScreenOptional) {
         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
-                pipBoundsAlgorithm, pipAnimationController);
+                pipBoundsAlgorithm);
         mContext = context;
         mPipTransitionState = pipTransitionState;
         mPipDisplayLayoutState = pipDisplayLayoutState;
+        mPipAnimationController = pipAnimationController;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -148,6 +153,13 @@
     }
 
     @Override
+    protected void onInit() {
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            mTransitions.addHandler(this);
+        }
+    }
+
+    @Override
     public void startExitTransition(int type, WindowContainerTransaction out,
             @Nullable Rect destinationBounds) {
         if (destinationBounds != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 64bba67..20c57fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -38,7 +38,8 @@
 import androidx.annotation.NonNull;
 
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
@@ -52,7 +53,6 @@
  */
 public abstract class PipTransitionController implements Transitions.TransitionHandler {
 
-    protected final PipAnimationController mPipAnimationController;
     protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
     protected final PipBoundsState mPipBoundsState;
     protected final ShellTaskOrganizer mShellTaskOrganizer;
@@ -135,22 +135,18 @@
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
-            PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipAnimationController pipAnimationController) {
+            PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) {
         mPipBoundsState = pipBoundsState;
         mPipMenuController = pipMenuController;
         mShellTaskOrganizer = shellTaskOrganizer;
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
-        mPipAnimationController = pipAnimationController;
         mTransitions = transitions;
-        if (!PipUtils.isPip2ExperimentEnabled()) {
-            if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-                shellInit.addInitCallback(this::onInit, this);
-            }
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            shellInit.addInitCallback(this::onInit, this);
         }
     }
 
-    private void onInit() {
+    protected void onInit() {
         mTransitions.addHandler(this);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index cc182ba..7606526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -38,10 +38,10 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipMediaController;
 import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMenuController;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 4a06d84..bc17ce9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -15,7 +15,7 @@
  */
 package com.android.wm.shell.pip.phone;
 
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -36,8 +36,8 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 
 import java.util.ArrayList;
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 ddea574..1064867 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
@@ -76,7 +76,12 @@
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -85,12 +90,7 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
index d7d335b..1b1ebc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
@@ -20,7 +20,7 @@
 import android.annotation.NonNull;
 import android.graphics.Rect;
 
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 8f0a8e1..c708b86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -20,10 +20,10 @@
 import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
 import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
 
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
 import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS;
 import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
 
@@ -41,8 +41,8 @@
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 4e687dd..e5f9fdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -46,10 +46,11 @@
 import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 
 import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index cb4e6c8..700a319 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -18,10 +18,10 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
 import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -50,12 +50,12 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index cd58ff4..a48e969f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -36,11 +36,11 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index 8d4a384..8a215b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.pip.tv;
 
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 4757efc..47a8cc8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -29,10 +29,10 @@
 import android.view.Gravity;
 import android.view.View;
 
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0816dc7..46336ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -48,11 +48,11 @@
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMediaController;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index a94bd6e..93f6826 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -21,12 +21,12 @@
 import android.graphics.Rect
 import android.util.Size
 import android.view.Gravity
-import com.android.wm.shell.pip.PipBoundsState
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -54,11 +54,11 @@
      *     the unstash timeout if already stashed.
      */
     data class Placement(
-        val bounds: Rect,
-        val anchorBounds: Rect,
-        @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
-        val unstashDestinationBounds: Rect? = null,
-        val triggerStash: Boolean = false
+            val bounds: Rect,
+            val anchorBounds: Rect,
+            @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
+            val unstashDestinationBounds: Rect? = null,
+            val triggerStash: Boolean = false
     ) {
         /** Bounds to use if the PiP should not be stashed. */
         fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 6720804..f315afb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -25,12 +25,12 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipMenuController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d3253a5..f24b2b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -21,8 +21,8 @@
 import androidx.annotation.NonNull;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTransition;
 import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
index 8ab85d0..b8e4c04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
@@ -17,27 +17,65 @@
 package com.android.wm.shell.pip2;
 
 import android.annotation.NonNull;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
 
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipMenuController;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 /** Placeholder, for demonstrate purpose only. */
-public abstract class PipTransition extends PipTransitionController {
+public class PipTransition extends PipTransitionController {
     public PipTransition(
             @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
             PipMenuController pipMenuController,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipAnimationController pipAnimationController) {
+            PipBoundsAlgorithm pipBoundsAlgorithm) {
         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
-                pipBoundsAlgorithm, pipAnimationController);
+                pipBoundsAlgorithm);
     }
+
+    @Override
+    protected void onInit() {
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            mTransitions.addHandler(this);
+        }
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        return null;
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        return false;
+    }
+
+    @Override
+    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {}
+
+    @Override
+    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishT) {}
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index bf1b7f9..46259a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -33,6 +33,11 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 4341c4c..d34e27b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -36,6 +36,8 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
index b9226d2..ac13d7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
@@ -25,6 +25,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 248d665..4e2b7f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -53,6 +53,11 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
index cc9e26b..8c7b47e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
@@ -29,8 +29,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 024cba3..3d5cd69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -33,8 +33,8 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 
 import org.junit.After;
 import org.junit.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 911f5e1..4eb5193 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,15 +55,16 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 8ce3ca4..0f8db85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -29,7 +29,7 @@
 import android.testing.AndroidTestingRunner;
 
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 12b4f3e..6777a5b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -37,13 +37,13 @@
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 314f195d..9aaabd1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,13 +33,13 @@
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
index 7370ed7..94f2b91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
@@ -25,7 +25,7 @@
 
 import com.android.wm.shell.R
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
 import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index da6951b..82fe5f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -27,9 +27,9 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
index aedf65d..998060f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -22,10 +22,10 @@
 import android.util.Size
 import android.view.Gravity
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index a5518eb..63a863e 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -160,6 +160,7 @@
     destroyHardwareResources();
     mAnimationContext->destroy();
     mRenderThread.cacheManager().onContextStopped(this);
+    mHintSessionWrapper.destroy();
 }
 
 static void setBufferCount(ANativeWindow* window) {
@@ -193,6 +194,7 @@
 void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
     ATRACE_CALL();
 
+    startHintSession();
     if (window) {
         mNativeSurface = std::make_unique<ReliableSurface>(window);
         mNativeSurface->init();
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 814ac4d..1f338ee 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -93,8 +93,17 @@
         : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
 
 HintSessionWrapper::~HintSessionWrapper() {
+    destroy();
+}
+
+void HintSessionWrapper::destroy() {
+    if (mHintSessionFuture.valid()) {
+        mHintSession = mHintSessionFuture.get();
+    }
     if (mHintSession) {
         gAPH_closeSessionFn(mHintSession);
+        mSessionValid = true;
+        mHintSession = nullptr;
     }
 }
 
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index 24b8150..bdb9959 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -37,6 +37,7 @@
     void sendLoadResetHint();
     void sendLoadIncreaseHint();
     bool init();
+    void destroy();
 
 private:
     APerformanceHintSession* mHintSession = nullptr;
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index dc93400..fd4a723 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -68,11 +68,11 @@
 private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
     key: TKey,
     value: TVal,
-    onNew: () -> Unit
+    onNew: (TVal) -> Unit
 ): TVal {
     val result = this.putIfAbsent(key, value)
     if (result == null) {
-        onNew()
+        onNew(value)
     }
     return result ?: value
 }
@@ -148,6 +148,8 @@
             override fun onPluginAttached(
                 manager: PluginLifecycleManager<ClockProviderPlugin>
             ): Boolean {
+                manager.isDebug = true
+
                 if (keepAllLoaded) {
                     // Always load new plugins if requested
                     return true
@@ -177,15 +179,22 @@
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(metadata, null, manager)) {
                             isClockListChanged = true
-                            onConnected(id)
+                            onConnected(it)
                         }
 
                     if (manager != info.manager) {
                         logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
-                            { str1 = id },
-                            { "Clock Id conflict on known attach: $str1 is double registered" }
+                            {
+                                str1 = id
+                                str2 = info.manager.toString()
+                                str3 = manager.toString()
+                            },
+                            {
+                                "Clock Id conflict on attach: " +
+                                    "$str1 is double registered by $str2 and $str3"
+                            }
                         )
                         continue
                     }
@@ -217,22 +226,29 @@
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
                             isClockListChanged = true
-                            onConnected(id)
+                            onConnected(it)
                         }
 
                     if (manager != info.manager) {
                         logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
-                            { str1 = id },
-                            { "Clock Id conflict on load: $str1 is double registered" }
+                            {
+                                str1 = id
+                                str2 = info.manager.toString()
+                                str3 = manager.toString()
+                            },
+                            {
+                                "Clock Id conflict on load: " +
+                                    "$str1 is double registered by $str2 and $str3"
+                            }
                         )
                         manager.unloadPlugin()
                         continue
                     }
 
                     info.provider = plugin
-                    onLoaded(id)
+                    onLoaded(info)
                 }
 
                 if (isClockListChanged) {
@@ -252,26 +268,33 @@
                         logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
-                            { str1 = id },
-                            { "Clock Id conflict on unload: $str1 is double registered" }
+                            {
+                                str1 = id
+                                str2 = info?.manager.toString()
+                                str3 = manager.toString()
+                            },
+                            {
+                                "Clock Id conflict on unload: " +
+                                    "$str1 is double registered by $str2 and $str3"
+                            }
                         )
                         continue
                     }
                     info.provider = null
-                    onUnloaded(id)
+                    onUnloaded(info)
                 }
 
                 verifyLoadedProviders()
             }
 
             override fun onPluginDetached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
-                val removed = mutableListOf<ClockId>()
+                val removed = mutableListOf<ClockInfo>()
                 availableClocks.entries.removeAll {
                     if (it.value.manager != manager) {
                         return@removeAll false
                     }
 
-                    removed.add(it.key)
+                    removed.add(it.value)
                     return@removeAll true
                 }
 
@@ -493,6 +516,12 @@
 
         scope.launch(bgDispatcher) {
             if (keepAllLoaded) {
+                logger.tryLog(
+                    TAG,
+                    LogLevel.INFO,
+                    {},
+                    { "verifyLoadedProviders: keepAllLoaded=true" }
+                )
                 // Enforce that all plugins are loaded if requested
                 for ((_, info) in availableClocks) {
                     info.manager?.loadPlugin()
@@ -503,6 +532,12 @@
 
             val currentClock = availableClocks[currentClockId]
             if (currentClock == null) {
+                logger.tryLog(
+                    TAG,
+                    LogLevel.INFO,
+                    {},
+                    { "verifyLoadedProviders: currentClock=null" }
+                )
                 // Current Clock missing, load no plugins and use default
                 for ((_, info) in availableClocks) {
                     info.manager?.unloadPlugin()
@@ -511,12 +546,13 @@
                 return@launch
             }
 
+            logger.tryLog(TAG, LogLevel.INFO, {}, { "verifyLoadedProviders: load currentClock" })
             val currentManager = currentClock.manager
             currentManager?.loadPlugin()
 
             for ((_, info) in availableClocks) {
                 val manager = info.manager
-                if (manager != null && manager.isLoaded && currentManager != manager) {
+                if (manager != null && currentManager != manager) {
                     manager.unloadPlugin()
                 }
             }
@@ -524,57 +560,68 @@
         }
     }
 
-    private fun onConnected(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.INFO,
-                { str1 = clockId },
-                { "Current clock ($str1) was connected" }
-            )
-        }
+    private fun onConnected(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
     }
 
-    private fun onLoaded(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
+    private fun onLoaded(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
 
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.INFO,
-                { str1 = clockId },
-                { "Current clock ($str1) was loaded" }
-            )
+        if (isCurrent) {
             triggerOnCurrentClockChanged()
         }
     }
 
-    private fun onUnloaded(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
+    private fun onUnloaded(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
 
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.WARNING,
-                { str1 = clockId },
-                { "Current clock ($str1) was unloaded" }
-            )
+        if (isCurrent) {
             triggerOnCurrentClockChanged()
         }
     }
 
-    private fun onDisconnected(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
-
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.WARNING,
-                { str1 = clockId },
-                { "Current clock ($str1) was disconnected" }
-            )
-        }
+    private fun onDisconnected(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
     }
 
     fun getClocks(): List<ClockMetadata> {
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
index 56c3f93..3e5e8a0 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -33,6 +33,12 @@
     /** Returns the currently loaded plugin instance (if plugin is loaded) */
     T getPlugin();
 
+    /** Returns true if the lifecycle manager should log debug messages */
+    boolean getIsDebug();
+
+    /** Sets whether or not hte lifecycle manager should log debug messages */
+    void setIsDebug(boolean debug);
+
     /** returns true if the plugin is currently loaded */
     default boolean isLoaded() {
         return getPlugin() != null;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 6b67c09..7719e95 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -55,7 +55,9 @@
     private final PluginListener<T> mListener;
     private final ComponentName mComponentName;
     private final PluginFactory<T> mPluginFactory;
+    private final String mTag;
 
+    private boolean mIsDebug = false;
     private Context mPluginContext;
     private T mPlugin;
 
@@ -71,27 +73,51 @@
         mComponentName = componentName;
         mPluginFactory = pluginFactory;
         mPlugin = plugin;
+        mTag = TAG + mComponentName.toShortString()
+                + '@' + Integer.toHexString(hashCode());
 
         if (mPlugin != null) {
             mPluginContext = mPluginFactory.createPluginContext();
         }
     }
 
+    @Override
+    public String toString() {
+        return mTag;
+    }
+
+    public boolean getIsDebug() {
+        return mIsDebug;
+    }
+
+    public void setIsDebug(boolean debug) {
+        mIsDebug = debug;
+    }
+
+    private void logDebug(String message) {
+        if (mIsDebug) {
+            Log.i(mTag, message);
+        }
+    }
+
     /** Alerts listener and plugin that the plugin has been created. */
     public void onCreate() {
         boolean loadPlugin = mListener.onPluginAttached(this);
         if (!loadPlugin) {
             if (mPlugin != null) {
+                logDebug("onCreate: auto-unload");
                 unloadPlugin();
             }
             return;
         }
 
         if (mPlugin == null) {
+            logDebug("onCreate auto-load");
             loadPlugin();
             return;
         }
 
+        logDebug("onCreate: load callbacks");
         mPluginFactory.checkVersion(mPlugin);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onCreate for plugins that aren't fragments, as fragments
@@ -103,6 +129,7 @@
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
     public void onDestroy() {
+        logDebug("onDestroy");
         unloadPlugin();
         mListener.onPluginDetached(this);
     }
@@ -118,15 +145,18 @@
      */
     public void loadPlugin() {
         if (mPlugin != null) {
+            logDebug("Load request when already loaded");
             return;
         }
 
         mPlugin = mPluginFactory.createPlugin();
         mPluginContext = mPluginFactory.createPluginContext();
         if (mPlugin == null || mPluginContext == null) {
+            Log.e(mTag, "Requested load, but failed");
             return;
         }
 
+        logDebug("Loaded plugin; running callbacks");
         mPluginFactory.checkVersion(mPlugin);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onCreate for plugins that aren't fragments, as fragments
@@ -143,9 +173,11 @@
      */
     public void unloadPlugin() {
         if (mPlugin == null) {
+            logDebug("Unload request when already unloaded");
             return;
         }
 
+        logDebug("Unloading plugin, running callbacks");
         mListener.onPluginUnloaded(mPlugin, this);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onDestroy for plugins that aren't fragments, as fragments
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 5d0e8f7..450010c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -48,6 +48,7 @@
     private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
     private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
     private static final float SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER = 1.4f;
+    private static final float SMARTSPACE_TOP_PADDING_MULTIPLIER = 2.625f;
 
     @IntDef({LARGE, SMALL})
     @Retention(RetentionPolicy.SOURCE)
@@ -92,6 +93,14 @@
     private KeyguardClockFrame mLargeClockFrame;
     private ClockController mClock;
 
+    // It's bc_smartspace_view, assigned by KeyguardClockSwitchController
+    // to get the top padding for translating smartspace for weather clock
+    private View mSmartspace;
+
+    // Smartspace in weather clock is translated by this value
+    // to compensate for the position invisible dateWeatherView
+    private int mSmartspaceTop = -1;
+
     private KeyguardStatusAreaView mStatusArea;
     private int mSmartspaceTopOffset;
     private float mWeatherClockSmartspaceScaling = 1f;
@@ -130,8 +139,11 @@
     public void onConfigChanged() {
         mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_clock_switch_y_shift);
-        mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
-                R.dimen.keyguard_smartspace_top_offset);
+        mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
+                        R.dimen.keyguard_smartspace_top_offset)
+                * mContext.getResources().getConfiguration().fontScale
+                / mContext.getResources().getDisplayMetrics().density
+                * SMARTSPACE_TOP_PADDING_MULTIPLIER);
         mWeatherClockSmartspaceScaling = ResourcesCompat.getFloat(
                 mContext.getResources(), R.dimen.weather_clock_smartspace_scale);
         mWeatherClockSmartspaceTranslateX = mContext.getResources().getDimensionPixelSize(
@@ -141,6 +153,12 @@
         updateStatusArea(/* animate= */false);
     }
 
+    /** Get bc_smartspace_view from KeyguardClockSwitchController
+     * Use its top to decide the translation value */
+    public void setSmartspace(View smartspace) {
+        mSmartspace = smartspace;
+    }
+
     /** Sets whether the large clock is being shown on a connected display. */
     public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
         if (mClock != null) {
@@ -291,7 +309,7 @@
                     && mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) {
                 statusAreaClockScale = mWeatherClockSmartspaceScaling;
                 statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX;
-                statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY;
+                statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY - mSmartspaceTop;
                 if (mSplitShadeCentered) {
                     statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER;
                 }
@@ -414,10 +432,14 @@
             post(() -> updateClockTargetRegions());
         }
 
-        if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+        if (mSmartspace != null && mSmartspaceTop != mSmartspace.getTop()) {
+            mSmartspaceTop = mSmartspace.getTop();
             post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
         }
 
+        if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+            post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
+        }
         mChildrenAreLaidOut = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 692b636..94f6dba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -371,6 +371,7 @@
         mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
 
         mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
+        mView.setSmartspace(mSmartspaceView);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e8b8f54..be08932 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -78,17 +78,6 @@
 
     private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
-    /**
-     * If the API caller or the user's personal preferences require explicit confirmation after
-     * successful authentication.
-     */
-    val isConfirmationRequired: Flow<Boolean> =
-        combine(_isOverlayTouched, promptSelectorInteractor.isConfirmationRequired) {
-            isOverlayTouched,
-            isConfirmationRequired ->
-            !isOverlayTouched && isConfirmationRequired
-        }
-
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
 
@@ -137,6 +126,15 @@
             }
             .distinctUntilChanged()
 
+    /**
+     * If the API caller or the user's personal preferences require explicit confirmation after
+     * successful authentication. Confirmation always required when in explicit flow.
+     */
+    val isConfirmationRequired: Flow<Boolean> =
+        combine(_isOverlayTouched, size) { isOverlayTouched, size ->
+            !isOverlayTouched && size.isNotSmall
+        }
+
     /** Title for the prompt. */
     val title: Flow<String> =
         promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -170,12 +168,7 @@
             .distinctUntilChanged()
 
     /** If the icon can be used as a confirmation button. */
-    val isIconConfirmButton: Flow<Boolean> =
-        combine(size, promptSelectorInteractor.isConfirmationRequired) {
-            size,
-            isConfirmationRequired ->
-            size.isNotSmall && isConfirmationRequired
-        }
+    val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged()
 
     /** If the negative button should be shown. */
     val isNegativeButtonVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7197b41..5a2c5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -39,6 +39,10 @@
     @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
 
     // 100 - notification
+    // TODO(b/297792660): Tracking Bug
+    val ADD_TRANSIENT_HUN_IN_STACK_STATE_ANIMATOR =
+        unreleasedFlag("add_transient_hun_in_stack_state_animator", teamfood = false)
+
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
         unreleasedFlag("notification_pipeline_developer_logging")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
new file mode 100644
index 0000000..629b361
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.haptics.slider
+
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** An event producer for a Seekable element such as the Android [SeekBar] */
+class SeekableSliderEventProducer : SliderEventProducer, OnSeekBarChangeListener {
+
+    /** The current event reported by a SeekBar */
+    private val _currentEvent = MutableStateFlow(SliderEvent(SliderEventType.NOTHING, 0f))
+
+    override fun produceEvents(): Flow<SliderEvent> = _currentEvent.asStateFlow()
+
+    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+        val eventType =
+            if (fromUser) SliderEventType.PROGRESS_CHANGE_BY_USER
+            else SliderEventType.PROGRESS_CHANGE_BY_PROGRAM
+
+        _currentEvent.value = SliderEvent(eventType, normalizeProgress(seekBar, progress))
+    }
+
+    /**
+     * Normalize the integer progress of a SeekBar to the range from 0F to 1F.
+     *
+     * @param[seekBar] The SeekBar that reports a progress.
+     * @param[progress] The integer progress of the SeekBar within its min and max values.
+     * @return The progress in the range from 0F to 1F.
+     */
+    private fun normalizeProgress(seekBar: SeekBar, progress: Int): Float {
+        if (seekBar.max == seekBar.min) {
+            return 1.0f
+        }
+        val range = seekBar.max - seekBar.min
+        return (progress - seekBar.min) / range.toFloat()
+    }
+
+    override fun onStartTrackingTouch(seekBar: SeekBar) {
+        _currentEvent.update { previousEvent ->
+            SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, previousEvent.currentProgress)
+        }
+    }
+
+    override fun onStopTrackingTouch(seekBar: SeekBar) {
+        _currentEvent.update { previousEvent ->
+            SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt
new file mode 100644
index 0000000..1377b29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.haptics.slider
+
+import androidx.annotation.FloatRange
+
+/**
+ * An event arising from a slider.
+ *
+ * @property[type] The type of event. Must be one of [SliderEventType].
+ * @property[currentProgress] The current progress of the slider normalized to the range between 0F
+ *   and 1F (inclusive).
+ */
+data class SliderEvent(
+    val type: SliderEventType,
+    @FloatRange(from = 0.0, to = 1.0) val currentProgress: Float
+)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt
new file mode 100644
index 0000000..8b17e86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.haptics.slider
+
+import kotlinx.coroutines.flow.Flow
+
+/** Defines a producer of [SliderEvent] to be consumed as a [Flow] */
+interface SliderEventProducer {
+
+    /**
+     * Produce a stream of [SliderEvent]
+     *
+     * @return A [Flow] of [SliderEvent] produced from the operation of a slider.
+     */
+    fun produceEvents(): Flow<SliderEvent>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
new file mode 100644
index 0000000..413e277
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -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.haptics.slider
+
+/** The type of a [SliderEvent]. */
+enum class SliderEventType {
+    /* No event. */
+    NOTHING,
+    /* The slider has captured a touch input and is tracking touch events. */
+    STARTED_TRACKING_TOUCH,
+    /* The slider progress is changing due to user touch input. */
+    PROGRESS_CHANGE_BY_USER,
+    /* The slider progress is changing programmatically. */
+    PROGRESS_CHANGE_BY_PROGRAM,
+    /* The slider has stopped tracking touch events. */
+    STOPPED_TRACKING_TOUCH,
+    /* The external (not touch) stimulus that was modifying the slider progress has stopped. */
+    EXTERNAL_STIMULUS_RELEASE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e982e65..ab294a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -480,6 +480,11 @@
     private final int mDreamOpenAnimationDuration;
 
     /**
+     * Whether unlock and wake should be sequenced.
+     */
+    private final boolean mOrderUnlockAndWake;
+
+    /**
      * The animation used for hiding keyguard. This is used to fetch the animation timings if
      * WindowManager is not providing us with them.
      */
@@ -1455,6 +1460,9 @@
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
         mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
         mMainDispatcher = mainDispatcher;
+
+        mOrderUnlockAndWake = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_orderUnlockAndWake);
     }
 
     public void userActivity() {
@@ -2835,6 +2843,10 @@
 
     private void setUnlockAndWakeFromDream(boolean updatedValue,
             @WakeAndUnlockUpdateReason int reason) {
+        if (!mOrderUnlockAndWake) {
+            return;
+        }
+
         if (updatedValue == mUnlockingAndWakingFromDream) {
             return;
         }
@@ -2917,6 +2929,13 @@
                             null /* nonApps */, null /* finishedCallback */);
                 });
             }
+
+            // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
+            // dreaming. It's time to wake up.
+            if ((mDreamOverlayShowing || mUpdateMonitor.isDreaming()) && !mOrderUnlockAndWake) {
+                mPM.wakeUp(mSystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                        "com.android.systemui:UNLOCK_DREAMING");
+            }
         }
         Trace.endSection();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 8f884d24..053c9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -206,6 +206,11 @@
 
     override fun bind(recentTasks: List<RecentTask>) {
         recentsViewController.bind(recentTasks)
+        if (!hasWorkProfile()) {
+            // Make sure to refresh the adapter, to show/hide the recents view depending on whether
+            // there are recents or not.
+            mMultiProfilePagerAdapter.personalListAdapter.notifyDataSetChanged()
+        }
     }
 
     override fun returnSelectedApp(launchCookie: IBinder) {
@@ -248,9 +253,20 @@
 
     override fun shouldGetOnlyDefaultActivities() = false
 
-    override fun shouldShowContentPreview() = true
+    override fun shouldShowContentPreview() =
+        if (hasWorkProfile()) {
+            // When the user has a work profile, we can always set this to true, and the layout is
+            // adjusted automatically, and hide the recents view.
+            true
+        } else {
+            // When there is no work profile, we should only show the content preview if there are
+            // recents, otherwise the collapsed app selector will look empty.
+            recentsViewController.hasRecentTasks
+        }
 
-    override fun shouldShowContentPreviewWhenEmpty(): Boolean = true
+    override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview()
+
+    private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1
 
     override fun createMyUserIdProvider(): MyUserIdProvider =
         object : MyUserIdProvider() {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 64de9bd..5d732fb 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -35,8 +35,8 @@
 import javax.inject.Inject
 
 /**
- * Controller that handles view of the recent apps selector in the media projection activity.
- * It is responsible for creating and updating recent apps view.
+ * Controller that handles view of the recent apps selector in the media projection activity. It is
+ * responsible for creating and updating recent apps view.
  */
 @MediaProjectionAppSelectorScope
 class MediaProjectionRecentsViewController
@@ -51,15 +51,21 @@
     private var views: Views? = null
     private var lastBoundData: List<RecentTask>? = null
 
+    val hasRecentTasks: Boolean
+        get() = lastBoundData?.isNotEmpty() ?: false
+
     init {
         taskViewSizeProvider.addCallback(this)
     }
 
     fun createView(parent: ViewGroup): ViewGroup =
-        views?.root ?: createRecentViews(parent).also {
-            views = it
-            lastBoundData?.let { recents -> bind(recents) }
-        }.root
+        views?.root
+            ?: createRecentViews(parent)
+                .also {
+                    views = it
+                    lastBoundData?.let { recents -> bind(recents) }
+                }
+                .root
 
     fun bind(recentTasks: List<RecentTask>) {
         views?.apply {
@@ -88,7 +94,8 @@
                 .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
                 as ViewGroup
 
-        val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
+        val container =
+            recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
         container.setTaskHeightSize()
 
         val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index d2b62eb..e41fb18 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -21,7 +21,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 
 import android.app.IActivityManager;
@@ -52,6 +51,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -75,6 +75,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -102,6 +103,7 @@
     private final float mKeyguardMaxRefreshRate;
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardBypassController mKeyguardBypassController;
+    private final Executor mBackgroundExecutor;
     private final AuthController mAuthController;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
@@ -135,6 +137,7 @@
             ConfigurationController configurationController,
             KeyguardViewMediator keyguardViewMediator,
             KeyguardBypassController keyguardBypassController,
+            @Background Executor backgroundExecutor,
             SysuiColorExtractor colorExtractor,
             DumpManager dumpManager,
             KeyguardStateController keyguardStateController,
@@ -152,6 +155,7 @@
         mLpChanged = new LayoutParams();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardBypassController = keyguardBypassController;
+        mBackgroundExecutor = backgroundExecutor;
         mColorExtractor = colorExtractor;
         mScreenOffAnimationController = screenOffAnimationController;
         dumpManager.registerDumpable(getClass().getName(), this);
@@ -512,13 +516,14 @@
         applyWindowLayoutParams();
 
         if (mHasTopUi != mHasTopUiChanged) {
-            whitelistIpcs(() -> {
+            mHasTopUi = mHasTopUiChanged;
+            mBackgroundExecutor.execute(() -> {
                 try {
                     mActivityManager.setHasTopUi(mHasTopUiChanged);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to call setHasTopUi", e);
                 }
-                mHasTopUi = mHasTopUiChanged;
+
             });
         }
         notifyStateChangedCallbacks();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index adff681..3e99bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -177,6 +177,7 @@
     private final VibratorHelper mVibratorHelper;
     private final BiometricUnlockLogger mLogger;
     private final SystemClock mSystemClock;
+    private final boolean mOrderUnlockAndWake;
 
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
@@ -316,6 +317,8 @@
         mLogger = biometricUnlockLogger;
         mSystemClock = systemClock;
         mFeatureFlags = featureFlags;
+        mOrderUnlockAndWake = resources.getBoolean(
+                com.android.internal.R.bool.config_orderUnlockAndWake);
 
         dumpManager.registerDumpable(getClass().getName(), this);
     }
@@ -470,10 +473,11 @@
             Trace.endSection();
         };
 
-        final boolean wakingFromDream = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
-                && mPowerManager.isInteractive();
+        final boolean wakeInKeyguard = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
+                && mPowerManager.isInteractive() && mOrderUnlockAndWake
+                && mOrderUnlockAndWake;
 
-        if (mMode != MODE_NONE && !wakingFromDream) {
+        if (mMode != MODE_NONE && !wakeInKeyguard) {
             wakeUp.run();
         }
         switch (mMode) {
@@ -509,7 +513,7 @@
                     // later to awaken.
                 }
                 mNotificationShadeWindowController.setNotificationShadeFocusable(false);
-                mKeyguardViewMediator.onWakeAndUnlocking(wakingFromDream);
+                mKeyguardViewMediator.onWakeAndUnlocking(wakeInKeyguard);
                 Trace.endSection();
                 break;
             case MODE_ONLY_WAKE:
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 47084c0..0ed46da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -20,6 +20,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
@@ -500,6 +501,81 @@
     }
 
     @Test
+    fun auto_confirm_authentication_when_finger_down() = runGenericTest {
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+        // No icon button when face only, can't confirm before auth
+        if (!testCase.isFaceOnly) {
+            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+        }
+        viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+        val authenticating by collectLastValue(viewModel.isAuthenticating)
+        val authenticated by collectLastValue(viewModel.isAuthenticated)
+        val message by collectLastValue(viewModel.message)
+        val size by collectLastValue(viewModel.size)
+        val legacyState by collectLastValue(viewModel.legacyState)
+        val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+        assertThat(authenticating).isFalse()
+        assertThat(canTryAgain).isFalse()
+        assertThat(authenticated?.isAuthenticated).isTrue()
+
+        if (testCase.isFaceOnly && expectConfirmation) {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+
+            assertThat(size).isEqualTo(PromptSize.MEDIUM)
+            assertButtonsVisible(
+                cancel = true,
+                confirm = true,
+            )
+
+            viewModel.confirmAuthenticated()
+            assertThat(message).isEqualTo(PromptMessage.Empty)
+            assertButtonsVisible()
+        } else {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        }
+    }
+
+    @Test
+    fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+        // No icon button when face only, can't confirm before auth
+        if (!testCase.isFaceOnly) {
+            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
+        }
+        viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+        val authenticating by collectLastValue(viewModel.isAuthenticating)
+        val authenticated by collectLastValue(viewModel.isAuthenticated)
+        val message by collectLastValue(viewModel.message)
+        val size by collectLastValue(viewModel.size)
+        val legacyState by collectLastValue(viewModel.legacyState)
+        val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+        assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+        if (expectConfirmation) {
+            assertThat(size).isEqualTo(PromptSize.MEDIUM)
+            assertButtonsVisible(
+                cancel = true,
+                confirm = true,
+            )
+
+            viewModel.confirmAuthenticated()
+            assertThat(message).isEqualTo(PromptMessage.Empty)
+            assertButtonsVisible()
+        }
+
+        assertThat(authenticating).isFalse()
+        assertThat(authenticated?.isAuthenticated).isTrue()
+        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        assertThat(canTryAgain).isFalse()
+    }
+
+    @Test
     fun cannot_confirm_unless_authenticated() = runGenericTest {
         val authenticating by collectLastValue(viewModel.isAuthenticating)
         val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -679,6 +755,10 @@
         testScope.runTest { block() }
     }
 
+    /** Obtain a MotionEvent with the specified MotionEvent action constant */
+    private fun obtainMotionEvent(action: Int): MotionEvent =
+        MotionEvent.obtain(0, 0, action, 0f, 0f, 0)
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
new file mode 100644
index 0000000..71a56cd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.haptics.slider
+
+import android.widget.SeekBar
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SeekableSliderEventProducerTest : SysuiTestCase() {
+
+    private val seekBar = SeekBar(mContext)
+    private val eventProducer = SeekableSliderEventProducer()
+    private val eventFlow = eventProducer.produceEvents()
+
+    @Test
+    fun onStartTrackingTouch_noProgress_trackingTouchEventProduced() = runTest {
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onStartTrackingTouch(seekBar)
+
+        assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0F), latest)
+    }
+
+    @Test
+    fun onStopTrackingTouch_noProgress_StoppedTrackingTouchEventProduced() = runTest {
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onStopTrackingTouch(seekBar)
+
+        assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0F), latest)
+    }
+
+    @Test
+    fun onProgressChangeByUser_changeByUserEventProduced_withNormalizedProgress() = runTest {
+        val progress = 50
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onProgressChanged(seekBar, progress, true)
+
+        assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 0.5F), latest)
+    }
+
+    @Test
+    fun onProgressChangeByUser_zeroWidthSlider_changeByUserEventProduced_withMaxProgress() =
+        runTest {
+            // No-width slider where the min and max values are the same
+            seekBar.min = 100
+            seekBar.max = 100
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, true)
+
+            assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 1.0F), latest)
+        }
+
+    @Test
+    fun onProgressChangeByProgram_changeByProgramEventProduced_withNormalizedProgress() = runTest {
+        val progress = 50
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onProgressChanged(seekBar, progress, false)
+
+        assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 0.5F), latest)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_zeroWidthSlider_changeByProgramEventProduced_withMaxProgress() =
+        runTest {
+            // No-width slider where the min and max values are the same
+            seekBar.min = 100
+            seekBar.max = 100
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, false)
+
+            assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 1.0F), latest)
+        }
+
+    @Test
+    fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced_withNormalizedProgress() =
+        runTest {
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, true)
+            eventProducer.onStartTrackingTouch(seekBar)
+
+            assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0.5F), latest)
+        }
+
+    @Test
+    fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced_withNormalizedProgress() =
+        runTest {
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, true)
+            eventProducer.onStopTrackingTouch(seekBar)
+
+            assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 0f4af2d..f73804d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -221,7 +221,7 @@
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mViewMediator, mKeyguardBypassController,
-                mColorExtractor, mDumpManager, mKeyguardStateController,
+                mUiBgExecutor, mColorExtractor, mDumpManager, mKeyguardStateController,
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
         mFeatureFlags = new FakeFeatureFlags();
@@ -334,10 +334,34 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
-    public void wakeupFromDreamingWhenKeyguardHides() {
+    public void wakeupFromDreamingWhenKeyguardHides_orderUnlockAndWakeOff() {
+        createAndStartViewMediator(false);
+
         mViewMediator.onSystemReady();
         TestableLooper.get(this).processAllMessages();
 
+        // Given device is dreaming
+        when(mUpdateMonitor.isDreaming()).thenReturn(true);
+
+        // When keyguard is going away
+        mKeyguardStateController.notifyKeyguardGoingAway(true);
+
+        // And keyguard is disabled which will call #handleHide
+        mViewMediator.setKeyguardEnabled(false);
+        TestableLooper.get(this).processAllMessages();
+
+        // Then dream should wake up
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(),
+                eq("com.android.systemui:UNLOCK_DREAMING"));
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void wakeupFromDreamingWhenKeyguardHides_orderUnlockAndWakeOn() {
+        createAndStartViewMediator(true);
+
+        mViewMediator.onSystemReady();
+        TestableLooper.get(this).processAllMessages();
         when(mPowerManager.isInteractive()).thenReturn(true);
 
         // Given device is dreaming
@@ -727,6 +751,9 @@
 
     @Test
     public void testWakeAndUnlockingOverDream() {
+        // Ensure ordering unlock and wake is enabled.
+        createAndStartViewMediator(true);
+
         // Send signal to wake
         mViewMediator.onWakeAndUnlocking(true);
 
@@ -756,6 +783,9 @@
 
     @Test
     public void testWakeAndUnlockingOverDream_signalAuthenticateIfStillShowing() {
+        // Ensure ordering unlock and wake is enabled.
+        createAndStartViewMediator(true);
+
         // Send signal to wake
         mViewMediator.onWakeAndUnlocking(true);
 
@@ -915,6 +945,13 @@
         verify(mStatusBarKeyguardViewManager).reset(true);
     }
     private void createAndStartViewMediator() {
+        createAndStartViewMediator(false);
+    }
+
+    private void createAndStartViewMediator(boolean orderUnlockAndWake) {
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_orderUnlockAndWake, orderUnlockAndWake);
+
         mViewMediator = new KeyguardViewMediator(
                 mContext,
                 mUiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index cde6ac0..c10333d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -62,6 +62,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import com.google.common.util.concurrent.MoreExecutors;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -72,6 +74,7 @@
 import org.mockito.Spy;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -97,6 +100,7 @@
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
+    private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
 
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private float mPreferredRefreshRate = -1;
@@ -117,7 +121,7 @@
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
-                mColorExtractor, mDumpManager, mKeyguardStateController,
+                mBackgroundExecutor, mColorExtractor, mDumpManager, mKeyguardStateController,
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger) {
                     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 481f7f7..5107ecc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -135,7 +135,6 @@
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
         mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
-        TestableResources res = getContext().getOrCreateTestableResources();
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
@@ -145,7 +144,16 @@
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
         when(mVibratorHelper.hasVibrator()).thenReturn(true);
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
-        mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
+        mBiometricUnlockController = createController(false);
+        when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
+        when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(mViewRootImpl);
+    }
+
+    BiometricUnlockController createController(boolean orderUnlockAndWake) {
+        TestableResources res = getContext().getOrCreateTestableResources();
+        res.addOverride(com.android.internal.R.bool.config_orderUnlockAndWake, orderUnlockAndWake);
+        BiometricUnlockController biometricUnlockController = new BiometricUnlockController(
+                mDozeScrimController,
                 mKeyguardViewMediator,
                 mNotificationShadeWindowController, mKeyguardStateController, mHandler,
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController,
@@ -156,10 +164,10 @@
                 mSystemClock,
                 mFeatureFlags
         );
-        mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
-        mBiometricUnlockController.addListener(mBiometricUnlockEventsListener);
-        when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
-        when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(mViewRootImpl);
+        biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
+        biometricUnlockController.addListener(mBiometricUnlockEventsListener);
+
+        return biometricUnlockController;
     }
 
     @Test
@@ -667,6 +675,7 @@
     }
     @Test
     public void onSideFingerprintSuccess_dreaming_unlockNoWake() {
+        mBiometricUnlockController = createController(true);
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
         when(mWakefulnessLifecycle.getLastWakeReason())
                 .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index e10a80c..0c69e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -333,7 +333,7 @@
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
-                mColorExtractor, mDumpManager, mKeyguardStateController,
+                syncExecutor, mColorExtractor, mDumpManager, mKeyguardStateController,
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
         mNotificationShadeWindowController.setWindowRootView(mNotificationShadeWindowView);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 4688658..423b85f 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -56,7 +56,7 @@
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
 
-    private final FillServiceCallbacks mCallbacks;
+    private FillServiceCallbacks mCallbacks;
     private final Object mLock = new Object();
     private CompletableFuture<FillResponse> mPendingFillRequest;
     private int mPendingFillRequestId = INVALID_REQUEST_ID;
@@ -128,9 +128,12 @@
      */
     public int cancelCurrentRequest() {
         synchronized (mLock) {
-            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
+            int canceledRequestId = mPendingFillRequest != null && mPendingFillRequest.cancel(false)
                     ? mPendingFillRequestId
                     : INVALID_REQUEST_ID;
+            mPendingFillRequest = null;
+            mPendingFillRequestId = INVALID_REQUEST_ID;
+            return canceledRequestId;
         }
     }
 
@@ -184,6 +187,10 @@
                 mPendingFillRequest = null;
                 mPendingFillRequestId = INVALID_REQUEST_ID;
             }
+            if (mCallbacks == null) {
+                Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+                return;
+            }
             if (err == null) {
                 mCallbacks.onFillRequestSuccess(request.getId(), res,
                         mComponentName.getPackageName(), request.getFlags());
@@ -220,6 +227,10 @@
             return save;
         }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS)
                 .whenComplete((res, err) -> Handler.getMain().post(() -> {
+                    if (mCallbacks == null) {
+                        Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+                        return;
+                    }
                     if (err == null) {
                         mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res);
                     } else {
@@ -234,6 +245,8 @@
     }
 
     public void destroy() {
+        cancelCurrentRequest();
         unbind();
+        mCallbacks = null;
     }
 }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index be514c5..db93323 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1513,7 +1513,7 @@
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                         pid,
                         (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
-                        new Pair<String, Integer>(app.processName, reason)));
+                        new Pair<ProcessRecord, Integer>(app, reason)));
         }
     }
 
@@ -2151,11 +2151,12 @@
                 case REPORT_UNFREEZE_MSG: {
                     int pid = msg.arg1;
                     int frozenDuration = msg.arg2;
-                    Pair<String, Integer> obj = (Pair<String, Integer>) msg.obj;
-                    String processName = obj.first;
+                    Pair<ProcessRecord, Integer> obj = (Pair<ProcessRecord, Integer>) msg.obj;
+                    ProcessRecord app = obj.first;
+                    String processName = app.processName;
                     int reason = obj.second;
 
-                    reportUnfreeze(pid, frozenDuration, processName, reason);
+                    reportUnfreeze(app, pid, frozenDuration, processName, reason);
                 } break;
                 case UID_FROZEN_STATE_CHANGED_MSG: {
                     final boolean frozen = (msg.arg1 == 1);
@@ -2350,10 +2351,11 @@
             }
         }
 
-        private void reportUnfreeze(int pid, int frozenDuration, String processName,
-                @UnfreezeReason int reason) {
+        private void reportUnfreeze(ProcessRecord app, int pid, int frozenDuration,
+                String processName, @UnfreezeReason int reason) {
 
             EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
+            app.onProcessUnfrozen();
 
             // See above for why we're not taking mPhenotypeFlagLock here
             if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 5ad49a4..db74f1a 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -225,6 +225,32 @@
         mBaseProcessTracker = baseProcessTracker;
     }
 
+    void onProcessFrozen() {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                final PackageList pkgList = mApp.getPkgList();
+                final long now = SystemClock.uptimeMillis();
+                synchronized (pkgList) {
+                    tracker.onProcessFrozen(now, pkgList.getPackageListLocked());
+                }
+            }
+        }
+    }
+
+    void onProcessUnfrozen() {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                final PackageList pkgList = mApp.getPkgList();
+                final long now = SystemClock.uptimeMillis();
+                synchronized (pkgList) {
+                    tracker.onProcessUnfrozen(now, pkgList.getPackageListLocked());
+                }
+            }
+        }
+    }
+
     void onProcessActive(IApplicationThread thread, ProcessStatsService tracker) {
         if (mThread == null) {
             synchronized (mProfilerLock) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f6acc41..99ba098 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1354,6 +1354,15 @@
         return false;
     }
 
+    void onProcessFrozen() {
+        mProfile.onProcessFrozen();
+    }
+
+    void onProcessUnfrozen() {
+        mProfile.onProcessUnfrozen();
+    }
+
+
     /*
      *  Delete all packages from list except the package indicated in info
      */
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b5cab17..0bb78f0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -257,24 +257,19 @@
         sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
     }
 
-    /*package*/ void disconnectAllBluetoothProfiles() {
-        synchronized (mDeviceStateLock) {
-            mBtHelper.disconnectAllBluetoothProfiles();
-        }
-    }
-
     /**
      * Handle BluetoothHeadset intents where the action is one of
      *   {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
      *   {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
      * @param intent
      */
-    /*package*/ void receiveBtEvent(@NonNull Intent intent) {
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                mBtHelper.receiveBtEvent(intent);
-            }
-        }
+    private void onReceiveBtEvent(@NonNull Intent intent) {
+        mBtHelper.onReceiveBtEvent(intent);
+    }
+
+    @GuardedBy("mDeviceStateLock")
+    /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
+        mBtHelper.onSetBtScoActiveDevice(btDevice);
     }
 
     /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
@@ -1404,14 +1399,14 @@
         sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info);
     }
 
-    /*package*/ void postScoAudioStateChanged(int state) {
-        sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
-    }
-
     /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
         sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice);
     }
 
+    /*package*/ void postReceiveBtEvent(Intent intent) {
+        sendLMsgNoDelay(MSG_L_RECEIVED_BT_EVENT, SENDMSG_QUEUE, intent);
+    }
+
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
         final int mUid; // Requester UID
@@ -1807,10 +1802,10 @@
                     }
                     break;
 
-                case MSG_I_SCO_AUDIO_STATE_CHANGED:
+                case MSG_L_RECEIVED_BT_EVENT:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            mBtHelper.onScoAudioStateChanged(msg.arg1);
+                            onReceiveBtEvent((Intent) msg.obj);
                         }
                     }
                     break;
@@ -1821,30 +1816,18 @@
                     }
                     break;
                 case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE:
-                    if (msg.arg1 != BluetoothProfile.HEADSET) {
+                    synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
                             mBtHelper.onBtProfileDisconnected(msg.arg1);
                             mDeviceInventory.onBtProfileDisconnected(msg.arg1);
                         }
-                    } else {
-                        synchronized (mSetModeLock) {
-                            synchronized (mDeviceStateLock) {
-                                mBtHelper.disconnectHeadset();
-                            }
-                        }
                     }
                     break;
                 case MSG_IL_BT_SERVICE_CONNECTED_PROFILE:
-                    if (msg.arg1 != BluetoothProfile.HEADSET) {
+                    synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
                             mBtHelper.onBtProfileConnected(msg.arg1, (BluetoothProfile) msg.obj);
                         }
-                    } else {
-                        synchronized (mSetModeLock) {
-                            synchronized (mDeviceStateLock) {
-                                mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
-                            }
-                        }
                     }
                     break;
                 case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: {
@@ -1978,7 +1961,6 @@
 
     private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
     private static final int MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
-    private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
 
     private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
     //
@@ -1994,6 +1976,8 @@
 
     private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;
 
+    private static final int MSG_L_RECEIVED_BT_EVENT = 55;
+
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 13e3fc7..da89502 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1489,8 +1489,12 @@
         }
     }
 
-    /*package*/ synchronized void onBtProfileDisconnected(int profile) {
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    /*package*/ void onBtProfileDisconnected(int profile) {
         switch (profile) {
+            case BluetoothProfile.HEADSET:
+                disconnectHeadset();
+                break;
             case BluetoothProfile.A2DP:
                 disconnectA2dp();
                 break;
@@ -1550,6 +1554,24 @@
         disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
     }
 
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    private void disconnectHeadset() {
+        boolean disconnect = false;
+        synchronized (mDevicesLock) {
+            for (DeviceInfo di : mConnectedDevices.values()) {
+                if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                    // There is only one HFP active device and setting the active
+                    // device to null will disconnect both in and out devices
+                    disconnect = true;
+                    break;
+                }
+            }
+        }
+        if (disconnect) {
+            mDeviceBroker.onSetBtScoActiveDevice(null);
+        }
+    }
+
     // must be called before removing the device from mConnectedDevices
     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
     // from AudioSystem
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 646228e..ef098e3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -61,7 +61,6 @@
 import android.app.UidObserver;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
@@ -1366,7 +1365,6 @@
         intentFilter.addAction(Intent.ACTION_USER_BACKGROUND);
         intentFilter.addAction(Intent.ACTION_USER_FOREGROUND);
         intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
-        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         intentFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
 
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
@@ -9570,7 +9568,7 @@
                 mDockState = dockState;
             } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)
                     || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                mDeviceBroker.receiveBtEvent(intent);
+                mDeviceBroker.postReceiveBtEvent(intent);
             } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 if (mMonitorRotation) {
                     RotationHelper.enable();
@@ -9638,15 +9636,6 @@
                 } catch (IllegalArgumentException e) {
                     Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
                 }
-            } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
-                sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                        "BluetoothAdapter ACTION_STATE_CHANGED with state " + state));
-
-                if (state == BluetoothAdapter.STATE_OFF ||
-                        state == BluetoothAdapter.STATE_TURNING_OFF) {
-                    mDeviceBroker.disconnectAllBluetoothProfiles();
-                }
             } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
                     action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
                 handleAudioEffectBroadcast(context, intent);
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index b350363..1462b3c 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -192,9 +192,13 @@
             adapter.getProfileProxy(mDeviceBroker.getContext(),
                     mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
             adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.A2DP_SINK);
+            adapter.getProfileProxy(mDeviceBroker.getContext(),
                     mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
             adapter.getProfileProxy(mDeviceBroker.getContext(),
                     mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO);
+            adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
         }
     }
 
@@ -261,27 +265,27 @@
 
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    /*package*/ synchronized void receiveBtEvent(Intent intent) {
+    /*package*/ synchronized void onReceiveBtEvent(Intent intent) {
         final String action = intent.getAction();
 
-        Log.i(TAG, "receiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState);
+        Log.i(TAG, "onReceiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState);
         if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
-            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class);
-            setBtScoActiveDevice(btDevice);
+            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE,
+                    android.bluetooth.BluetoothDevice.class);
+            onSetBtScoActiveDevice(btDevice);
         } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
             int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-            Log.i(TAG,"receiveBtEvent ACTION_AUDIO_STATE_CHANGED: "+btState);
-            mDeviceBroker.postScoAudioStateChanged(btState);
+            onScoAudioStateChanged(btState);
         }
     }
 
     /**
-     * Exclusively called from AudioDeviceBroker when handling MSG_I_SCO_AUDIO_STATE_CHANGED
+     * Exclusively called from AudioDeviceBroker when handling MSG_L_RECEIVED_BT_EVENT
      * as part of the serialization of the communication route selection
      */
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    void onScoAudioStateChanged(int state) {
+    private void onScoAudioStateChanged(int state) {
         boolean broadcast = false;
         int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
         switch (state) {
@@ -294,10 +298,10 @@
                     // broadcast intent if the connection was initated by AudioService
                     broadcast = true;
                 }
-                mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
+                mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
                 break;
             case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
+                mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
                 scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
                 // There are two cases where we want to immediately reconnect audio:
                 // 1) If a new start request was received while disconnecting: this was
@@ -431,15 +435,6 @@
         mScoConnectionState = state;
     }
 
-    /*package*/ synchronized void disconnectAllBluetoothProfiles() {
-        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.A2DP);
-        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.A2DP_SINK);
-        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEADSET);
-        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEARING_AID);
-        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO);
-        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO_BROADCAST);
-    }
-
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void resetBluetoothSco() {
@@ -450,18 +445,14 @@
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    /*package*/ synchronized void disconnectHeadset() {
-        setBtScoActiveDevice(null);
-        mBluetoothHeadset = null;
-    }
-
     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileDisconnected(int profile) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "BT profile " + BluetoothProfile.getProfileName(profile) + " disconnected"));
         switch (profile) {
+            case BluetoothProfile.HEADSET:
+                mBluetoothHeadset = null;
+                break;
             case BluetoothProfile.A2DP:
                 mA2dp = null;
                 break;
@@ -471,14 +462,10 @@
             case BluetoothProfile.LE_AUDIO:
                 mLeAudio = null;
                 break;
-
             case BluetoothProfile.A2DP_SINK:
             case BluetoothProfile.LE_AUDIO_BROADCAST:
-                // shouldn't be received here as profile doesn't involve BtHelper
-                Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper "
-                        + BluetoothProfile.getProfileName(profile));
+                // nothing to do in BtHelper
                 break;
-
             default:
                 // Not a valid profile to disconnect
                 Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
@@ -492,17 +479,31 @@
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
                 + proxy));
-        if (profile == BluetoothProfile.HEADSET) {
-            onHeadsetProfileConnected((BluetoothHeadset) proxy);
-            return;
+        switch (profile) {
+            case BluetoothProfile.HEADSET:
+                onHeadsetProfileConnected((BluetoothHeadset) proxy);
+                return;
+            case BluetoothProfile.A2DP:
+                mA2dp = (BluetoothA2dp) proxy;
+                break;
+            case BluetoothProfile.HEARING_AID:
+                mHearingAid = (BluetoothHearingAid) proxy;
+                break;
+            case BluetoothProfile.LE_AUDIO:
+                mLeAudio = (BluetoothLeAudio) proxy;
+                break;
+            case BluetoothProfile.A2DP_SINK:
+            case BluetoothProfile.LE_AUDIO_BROADCAST:
+                // nothing to do in BtHelper
+                return;
+            default:
+                // Not a valid profile to connect
+                Log.e(TAG, "onBtProfileConnected: Not a valid profile to connect "
+                        + BluetoothProfile.getProfileName(profile));
+                break;
         }
-        if (profile == BluetoothProfile.A2DP) {
-            mA2dp = (BluetoothA2dp) proxy;
-        } else if (profile == BluetoothProfile.HEARING_AID) {
-            mHearingAid = (BluetoothHearingAid) proxy;
-        } else if (profile == BluetoothProfile.LE_AUDIO) {
-            mLeAudio = (BluetoothLeAudio) proxy;
-        }
+
+        // this part is only for A2DP, LE Audio unicast and Hearing aid
         final List<BluetoothDevice> deviceList = proxy.getConnectedDevices();
         if (deviceList.isEmpty()) {
             return;
@@ -523,7 +524,7 @@
 
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
+    private void onHeadsetProfileConnected(BluetoothHeadset headset) {
         // Discard timeout message
         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
         mBluetoothHeadset = headset;
@@ -532,7 +533,7 @@
         if (adapter != null) {
             activeDevices = adapter.getActiveDevices(BluetoothProfile.HEADSET);
         }
-        setBtScoActiveDevice((activeDevices.size() > 0) ? activeDevices.get(0) : null);
+        onSetBtScoActiveDevice((activeDevices.size() > 0) ? activeDevices.get(0) : null);
         // Refresh SCO audio state
         checkScoAudioState();
         if (mScoAudioState != SCO_STATE_ACTIVATE_REQ
@@ -643,20 +644,19 @@
 
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    @GuardedBy("BtHelper.this")
-    private void setBtScoActiveDevice(BluetoothDevice btDevice) {
-        Log.i(TAG, "setBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
+    /*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
+        Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
                 + " -> " + getAnonymizedAddress(btDevice));
         final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
         if (Objects.equals(btDevice, previousActiveDevice)) {
             return;
         }
         if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
-            Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+            Log.w(TAG, "onSetBtScoActiveDevice() failed to remove previous device "
                     + getAnonymizedAddress(previousActiveDevice));
         }
         if (!handleBtScoActiveDeviceChange(btDevice, true)) {
-            Log.e(TAG, "setBtScoActiveDevice() failed to add new device "
+            Log.e(TAG, "onSetBtScoActiveDevice() failed to add new device "
                     + getAnonymizedAddress(btDevice));
             // set mBluetoothHeadsetDevice to null when failing to add new device
             btDevice = null;
@@ -677,16 +677,14 @@
                         case BluetoothProfile.HEADSET:
                         case BluetoothProfile.HEARING_AID:
                         case BluetoothProfile.LE_AUDIO:
+                        case BluetoothProfile.A2DP_SINK:
+                        case BluetoothProfile.LE_AUDIO_BROADCAST:
                             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                                     "BT profile service: connecting "
                                     + BluetoothProfile.getProfileName(profile) + " profile"));
                             mDeviceBroker.postBtProfileConnected(profile, proxy);
                             break;
 
-                        case BluetoothProfile.A2DP_SINK:
-                            // no A2DP sink functionality handled by BtHelper
-                        case BluetoothProfile.LE_AUDIO_BROADCAST:
-                            // no broadcast functionality handled by BtHelper
                         default:
                             break;
                     }
@@ -698,16 +696,14 @@
                         case BluetoothProfile.HEADSET:
                         case BluetoothProfile.HEARING_AID:
                         case BluetoothProfile.LE_AUDIO:
+                        case BluetoothProfile.A2DP_SINK:
+                        case BluetoothProfile.LE_AUDIO_BROADCAST:
                             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                                     "BT profile service: disconnecting "
                                         + BluetoothProfile.getProfileName(profile) + " profile"));
                             mDeviceBroker.postBtProfileDisconnected(profile);
                             break;
 
-                        case BluetoothProfile.A2DP_SINK:
-                            // no A2DP sink functionality handled by BtHelper
-                        case BluetoothProfile.LE_AUDIO_BROADCAST:
-                            // no broadcast functionality handled by BtHelper
                         default:
                             break;
                     }
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 2a3302c..e8a20de 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -68,8 +68,10 @@
         @Override
         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
             if (userId != UserHandle.USER_NULL
                     && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
+                Slog.d(TAG, "Removing data for user: " + userId);
                 onUserRemoved(userId);
             }
         }
@@ -84,7 +86,9 @@
         mModality = modality;
         mBiometricNotification = biometricNotification;
 
-        context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        context.registerReceiver(mBroadcastReceiver, intentFilter);
     }
 
     private void initializeUserAuthenticationStatsMap() {
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
index 74e1410..8122b1d 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -59,7 +59,7 @@
         // The package info in the context isn't initialized in the way it is for normal apps,
         // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
         // build the path manually below using the same policy that appears in ContextImpl.
-        final File prefsFile = new File(Environment.getDataSystemDeDirectory(), FILE_NAME);
+        final File prefsFile = new File(Environment.getDataSystemDirectory(), FILE_NAME);
         mSharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
     }
 
@@ -137,16 +137,19 @@
                     iterator.remove();
                     break;
                 }
+                // Reset frrStatJson when user doesn't exist.
+                frrStatJson = null;
             }
 
-            // If there's existing frr stats in the file, we want to update the stats for the given
-            // modality and keep the stats for other modalities.
+            // Checks if this is a new user and there's no JSON for this user in the storage.
             if (frrStatJson == null) {
                 frrStatJson = new JSONObject().put(USER_ID, userId);
             }
             frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
                     enrollmentNotifications, modality));
 
+            Slog.d(TAG, "frrStatsSet to persist: " + frrStatsSet);
+
             mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
 
         } catch (JSONException e) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index dd434fbe..3e4dd16 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3611,8 +3611,8 @@
 
             // Otherwise check persisted shortcuts
             getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> {
-                cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage,
-                        packageName, si, userId));
+                cb.complete(si == null ? null : getShortcutIconUriInternal(launcherUserId,
+                        launcherPackage, packageName, si, userId));
             });
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 062126d..4c58e23 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -225,7 +225,6 @@
 import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
 import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
-import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -2717,12 +2716,14 @@
      */
     void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
         removeTransferSplashScreenTimeout();
-        // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
-        // either way, abort and reset the sequence.
-        if (parcelable == null
+        final SurfaceControl windowAnimationLeash = (parcelable == null
                 || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
                 || mStartingWindow == null || mStartingWindow.mRemoved
-                || finishing) {
+                || finishing) ? null
+                : TaskOrganizerController.applyStartingWindowAnimation(mStartingWindow);
+        if (windowAnimationLeash == null) {
+            // Unable to copy from shell, maybe it's not a splash screen, or something went wrong.
+            // Either way, abort and reset the sequence.
             if (parcelable != null) {
                 parcelable.clearIfNeeded();
             }
@@ -2730,9 +2731,6 @@
             removeStartingWindow();
             return;
         }
-        // schedule attach splashScreen to client
-        final SurfaceControl windowAnimationLeash = TaskOrganizerController
-                .applyStartingWindowAnimation(mStartingWindow);
         try {
             mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
@@ -2861,7 +2859,6 @@
         } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
             removeStartingWindow();
         }
-        lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
     }
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index a23547e..2d281c4 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -76,7 +76,7 @@
      * This starting window should be removed after applying the start transaction of transition,
      * which ensures the app window has shown.
      */
-    @AfterTransaction int mRemoveAfterTransaction;
+    @AfterTransaction int mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
 
     /** Whether to prepare the removal animation. */
     boolean mPrepareRemoveAnimation;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 44632c9..c49a1d0 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1127,6 +1127,9 @@
                         // though, notify the controller to prevent degenerate cases.
                         if (!r.isVisibleRequested()) {
                             mController.mValidateCommitVis.add(r);
+                        } else {
+                            // Make sure onAppTransitionFinished can be notified.
+                            mParticipants.add(r);
                         }
                         return;
                     }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 31afcbf..4d73358 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -519,7 +519,8 @@
         for (int i = mFixedRotationTransformState.mAssociatedTokens.size() - 1; i >= 0; i--) {
             final ActivityRecord r =
                     mFixedRotationTransformState.mAssociatedTokens.get(i).asActivityRecord();
-            if (r != null && r.isInTransition()) {
+            // Only care about the transition at Activity/Task level.
+            if (r != null && r.inTransitionSelfOrParent() && !r.mDisplayContent.inTransition()) {
                 return true;
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
index dde2a3c..0c0d47a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -217,6 +217,31 @@
     }
 
     @Test
+    public void persistFrrStats_multiUser_newUser_shouldUpdateRecord() throws JSONException {
+        AuthenticationStats authenticationStats1 = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        AuthenticationStats authenticationStats2 = new AuthenticationStats(USER_ID_2,
+                100 /* totalAttempts */, 5 /* rejectedAttempts */,
+                1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+        // Sets up the shared preference with user 1 only.
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(authenticationStats1)));
+
+        // Add data for user 2.
+        mAuthenticationStatsPersister.persistFrrStats(authenticationStats2.getUserId(),
+                authenticationStats2.getTotalAttempts(),
+                authenticationStats2.getRejectedAttempts(),
+                authenticationStats2.getEnrollmentNotifications(),
+                authenticationStats2.getModality());
+
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue())
+                .contains(buildFrrStats(authenticationStats2));
+    }
+
+    @Test
     public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
         AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
                 300 /* totalAttempts */, 10 /* rejectedAttempts */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9cc4117..9b22efd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1703,14 +1703,17 @@
         final Task task = app.getTask();
         final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
         mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
-        doReturn(true).when(app).isInTransition();
+        doReturn(true).when(app).inTransitionSelfOrParent();
         // If the task contains a transition, this should be no-op.
         mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
 
         assertTrue(app2.hasFixedRotationTransform());
         assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
 
-        doReturn(false).when(app).isInTransition();
+        // The display should be unlikely to be in transition, but if it happens, the fixed
+        // rotation should proceed to finish because the activity/task level transition is finished.
+        doReturn(true).when(mDisplayContent).inTransition();
+        doReturn(false).when(app).inTransitionSelfOrParent();
         // Although this notifies app instead of app2 that uses the fixed rotation, app2 should
         // still finish the transform because there is no more transition event.
         mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3703349..60d640c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1992,7 +1992,7 @@
             "lte_plus_threshold_bandwidth_khz_int";
 
     /**
-     * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+     * The combined channel bandwidth threshold (inclusive) in KHz required to display the
      * NR advanced (i.e. 5G+) data icon. It is 0 by default, meaning minimum bandwidth check is
      * not enabled. Other factors like bands or frequency can also determine whether the NR
      * advanced data icon is shown or not.
diff --git a/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java b/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
new file mode 100644
index 0000000..433e88c
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
@@ -0,0 +1,318 @@
+/*
+ * 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 android.net.wifi.nl80211;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class InstantWifi {
+    private static final String INSTANT_WIFI_TAG = "InstantWifi";
+    private static final int OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS = 1000;
+    private static final int WIFI_NETWORK_EXPIRED_MS = 7 * 24 * 60 * 60 * 1000; // a week
+    private static final String NO_CONNECTION_TIMEOUT_ALARM_TAG =
+            INSTANT_WIFI_TAG + " No Connection Timeout";
+
+    private Context mContext;
+    private AlarmManager mAlarmManager;
+    private Handler mEventHandler;
+    private ConnectivityManager mConnectivityManager;
+    private WifiManager mWifiManager;
+    private PowerManager mPowerManager;
+    private long mLastWifiOnSinceBootMs;
+    private long mLastScreenOnSinceBootMs;
+    private boolean mIsWifiConnected = false;
+    private boolean mScreenOn = false;
+    private boolean mWifiEnabled = false;
+    private boolean mIsNoConnectionAlarmSet = false;
+    private ArrayList<WifiNetwork> mConnectedWifiNetworkList = new ArrayList<>();
+    private AlarmManager.OnAlarmListener mNoConnectionTimeoutCallback =
+            new AlarmManager.OnAlarmListener() {
+                public void onAlarm() {
+                    Log.i(INSTANT_WIFI_TAG, "Timed out waiting for wifi connection");
+                    mIsNoConnectionAlarmSet = false;
+                    mWifiManager.startScan();
+                }
+            };
+
+    public InstantWifi(Context context, AlarmManager alarmManager, Handler eventHandler) {
+        mContext = context;
+        mAlarmManager = alarmManager;
+        mEventHandler = eventHandler;
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mConnectivityManager.registerNetworkCallback(
+                new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build(), new WifiNetworkCallback());
+        // System power service was initialized before wifi nl80211 service.
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        IntentFilter screenEventfilter = new IntentFilter();
+        screenEventfilter.addAction(Intent.ACTION_SCREEN_ON);
+        screenEventfilter.addAction(Intent.ACTION_SCREEN_OFF);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            if (!mScreenOn) {
+                                mLastScreenOnSinceBootMs = getMockableElapsedRealtime();
+                            }
+                            mScreenOn = true;
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            mScreenOn = false;
+                        }
+                        Log.d(INSTANT_WIFI_TAG, "mScreenOn is changed to " + mScreenOn);
+                    }
+                }, screenEventfilter, null, mEventHandler);
+        mScreenOn = mPowerManager.isInteractive();
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                                WifiManager.WIFI_STATE_UNKNOWN);
+                        mWifiEnabled = state == WifiManager.WIFI_STATE_ENABLED;
+                        if (mWifiEnabled) {
+                            mLastWifiOnSinceBootMs = getMockableElapsedRealtime();
+                        }
+                        Log.d(INSTANT_WIFI_TAG, "mWifiEnabled is changed to " + mWifiEnabled);
+                    }
+                },
+                new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION),
+                null, mEventHandler);
+    }
+
+    @VisibleForTesting
+    protected long getMockableElapsedRealtime() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    private class WifiNetwork {
+        private final int mNetId;
+        private Set<Integer> mConnectedFrequencies = new HashSet<Integer>();
+        private int[] mLastTwoConnectedFrequencies = new int[2];
+        private long mLastConnectedTimeMillis;
+        WifiNetwork(int netId) {
+            mNetId = netId;
+        }
+
+        public int getNetId() {
+            return mNetId;
+        }
+
+        public boolean addConnectedFrequency(int channelFrequency) {
+            mLastConnectedTimeMillis = getMockableElapsedRealtime();
+            if (mLastTwoConnectedFrequencies[0] != channelFrequency
+                    && mLastTwoConnectedFrequencies[1] != channelFrequency) {
+                mLastTwoConnectedFrequencies[0] = mLastTwoConnectedFrequencies[1];
+                mLastTwoConnectedFrequencies[1] = channelFrequency;
+            }
+            return mConnectedFrequencies.add(channelFrequency);
+        }
+
+        public Set<Integer> getConnectedFrequencies() {
+            return mConnectedFrequencies;
+        }
+
+        public int[] getLastTwoConnectedFrequencies() {
+            if ((getMockableElapsedRealtime() - mLastConnectedTimeMillis)
+                    > WIFI_NETWORK_EXPIRED_MS) {
+                return new int[0];
+            }
+            return mLastTwoConnectedFrequencies;
+        }
+
+        public long getLastConnectedTimeMillis() {
+            return mLastConnectedTimeMillis;
+        }
+    }
+
+    private class WifiNetworkCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(@NonNull Network network) {
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Network network,
+                NetworkCapabilities networkCapabilities) {
+            if (networkCapabilities != null && network != null) {
+                WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+                if (wifiInfo == null || mWifiManager == null) {
+                    return;
+                }
+                WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
+                if (config == null) {
+                    return;
+                }
+                final int currentNetworkId = config.networkId;
+                final int connectecFrequency = wifiInfo.getFrequency();
+                if (connectecFrequency < 0 || currentNetworkId < 0) {
+                    return;
+                }
+                mIsWifiConnected = true;
+                if (mIsNoConnectionAlarmSet) {
+                    mAlarmManager.cancel(mNoConnectionTimeoutCallback);
+                }
+                Log.d(INSTANT_WIFI_TAG, "Receive Wifi is connected, freq =  " + connectecFrequency
+                        + " and currentNetworkId : " + currentNetworkId
+                        + ", wifiinfo = " + wifiInfo);
+                boolean isExist = false;
+                for (WifiNetwork wifiNetwork : mConnectedWifiNetworkList) {
+                    if (wifiNetwork.getNetId() == currentNetworkId) {
+                        if (wifiNetwork.addConnectedFrequency(connectecFrequency)) {
+                            Log.d(INSTANT_WIFI_TAG, "Update connected frequency: "
+                                    + connectecFrequency + " to Network currentNetworkId : "
+                                    + currentNetworkId);
+                        }
+                        isExist = true;
+                    }
+                }
+                if (!isExist) {
+                    WifiNetwork currentNetwork = new WifiNetwork(currentNetworkId);
+                    currentNetwork.addConnectedFrequency(connectecFrequency);
+                    if (mConnectedWifiNetworkList.size() < 5) {
+                        mConnectedWifiNetworkList.add(currentNetwork);
+                    } else {
+                        ArrayList<WifiNetwork> lastConnectedWifiNetworkList = new ArrayList<>();
+                        WifiNetwork legacyNetwork = mConnectedWifiNetworkList.get(0);
+                        for (WifiNetwork connectedNetwork : mConnectedWifiNetworkList) {
+                            if (connectedNetwork.getNetId() == legacyNetwork.getNetId()) {
+                                continue;
+                            }
+                            // Keep the used recently network in the last connected list
+                            if (connectedNetwork.getLastConnectedTimeMillis()
+                                    > legacyNetwork.getLastConnectedTimeMillis()) {
+                                lastConnectedWifiNetworkList.add(connectedNetwork);
+                            } else {
+                                lastConnectedWifiNetworkList.add(legacyNetwork);
+                                legacyNetwork = connectedNetwork;
+                            }
+                        }
+                        mConnectedWifiNetworkList = lastConnectedWifiNetworkList;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            mIsWifiConnected = false;
+        }
+    }
+
+    /**
+     * Returns whether or not the scan freqs should be overrided by using predicted channels.
+     */
+    public boolean isUsePredictedScanningChannels() {
+        if (mIsWifiConnected || mConnectedWifiNetworkList.size() == 0
+                || !mWifiManager.isWifiEnabled() || !mPowerManager.isInteractive()) {
+            return false;
+        }
+        if (!mWifiEnabled || !mScreenOn) {
+            Log.d(INSTANT_WIFI_TAG, "WiFi/Screen State mis-match, run instant Wifi anyway!");
+            return true;
+        }
+        return (((getMockableElapsedRealtime() - mLastWifiOnSinceBootMs)
+                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS)
+                || ((getMockableElapsedRealtime() - mLastScreenOnSinceBootMs)
+                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS));
+    }
+
+    /**
+     * Overrides the frequenies in SingleScanSetting
+     *
+     * @param settings the SingleScanSettings will be overrided.
+     * @param freqs new frequencies of SingleScanSettings
+     */
+    @Nullable
+    public void overrideFreqsForSingleScanSettingsIfNecessary(
+            @Nullable SingleScanSettings settings, @Nullable Set<Integer> freqs) {
+        if (!isUsePredictedScanningChannels() || settings == null || freqs == null
+                || freqs.size() == 0) {
+            return;
+        }
+        if (settings.channelSettings == null) {
+            settings.channelSettings = new ArrayList<>();
+        } else {
+            settings.channelSettings.clear();
+        }
+        for (int freq : freqs) {
+            if (freq > 0) {
+                ChannelSettings channel = new ChannelSettings();
+                channel.frequency = freq;
+                settings.channelSettings.add(channel);
+            }
+        }
+        // Monitor connection after last override scan request.
+        if (mIsNoConnectionAlarmSet) {
+            mAlarmManager.cancel(mNoConnectionTimeoutCallback);
+        }
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                getMockableElapsedRealtime() + OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS,
+                NO_CONNECTION_TIMEOUT_ALARM_TAG, mNoConnectionTimeoutCallback, mEventHandler);
+        mIsNoConnectionAlarmSet = true;
+    }
+
+    /**
+     * Returns the predicted scanning chcnnels set.
+     */
+    @NonNull
+    public Set<Integer> getPredictedScanningChannels() {
+        Set<Integer> predictedScanChannels = new HashSet<>();
+        if (!isUsePredictedScanningChannels()) {
+            Log.d(INSTANT_WIFI_TAG, "Drop, size: " + mConnectedWifiNetworkList.size());
+            return predictedScanChannels;
+        }
+        for (WifiNetwork network : mConnectedWifiNetworkList) {
+            for (int connectedFrequency : network.getLastTwoConnectedFrequencies()) {
+                if (connectedFrequency > 0) {
+                    predictedScanChannels.add(connectedFrequency);
+                    Log.d(INSTANT_WIFI_TAG, "Add channel: " + connectedFrequency
+                            + " to predicted channel");
+                }
+            }
+        }
+        return predictedScanChannels;
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 2a199d2..ca12c4c 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -105,6 +105,8 @@
 
     // Cached wificond binder handlers.
     private IWificond mWificond;
+    private Context mContext;
+    private InstantWifi mInstantWifi;
     private WificondEventHandler mWificondEventHandler = new WificondEventHandler();
     private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
     private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
@@ -174,6 +176,12 @@
 
     /** @hide */
     @VisibleForTesting
+    protected InstantWifi getInstantWifiMockable() {
+        return mInstantWifi;
+    }
+
+    /** @hide */
+    @VisibleForTesting
     public class WificondEventHandler extends IWificondEventCallback.Stub {
         private Map<CountryCodeChangedListener, Executor> mCountryCodeChangedListenerHolder =
                 new HashMap<>();
@@ -419,6 +427,7 @@
     public WifiNl80211Manager(Context context) {
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mEventHandler = new Handler(context.getMainLooper());
+        mContext = context;
     }
 
     /**
@@ -434,6 +443,7 @@
         if (mWificond == null) {
             Log.e(TAG, "Failed to get reference to wificond");
         }
+        mContext = context;
     }
 
     /** @hide */
@@ -441,6 +451,7 @@
     public WifiNl80211Manager(Context context, IWificond wificond) {
         this(context);
         mWificond = wificond;
+        mContext = context;
     }
 
     /** @hide */
@@ -744,6 +755,9 @@
             Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
         }
 
+        if (getInstantWifiMockable() == null) {
+            mInstantWifi = new InstantWifi(mContext, mAlarmManager, mEventHandler);
+        }
         return true;
     }
 
@@ -1071,6 +1085,10 @@
         if (settings == null) {
             return false;
         }
+        if (getInstantWifiMockable() != null) {
+            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
+                    getInstantWifiMockable().getPredictedScanningChannels());
+        }
         try {
             return scannerImpl.scan(settings);
         } catch (RemoteException e1) {
@@ -1115,6 +1133,10 @@
         if (settings == null) {
             return WifiScanner.REASON_INVALID_ARGS;
         }
+        if (getInstantWifiMockable() != null) {
+            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
+                    getInstantWifiMockable().getPredictedScanningChannels());
+        }
         try {
             int status = scannerImpl.scanRequest(settings);
             return toFrameworkScanStatusCode(status);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java b/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
new file mode 100644
index 0000000..ebff0e2
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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 android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.InstantWifi}.
+ */
+@SmallTest
+public class InstantWifiTest {
+    @Mock private Context mContext;
+    @Mock private ConnectivityManager mMockConnectivityManager;
+    @Mock private WifiManager mMockWifiManager;
+    @Mock private Network mMockWifiNetwork;
+    @Mock private WifiInfo mMockWifiInfo;
+    @Mock private WifiConfiguration mMockWifiConfiguration;
+    @Mock private IPowerManager mPowerManagerService;
+    private InstantWifi mInstantWifi;
+    private TestLooper mLooper;
+    private Handler mHandler;
+    private TestAlarmManager mTestAlarmManager;
+    private AlarmManager mAlarmManager;
+    private PowerManager mMockPowerManager;
+
+    private final ArgumentCaptor<NetworkCallback> mWifiNetworkCallbackCaptor =
+            ArgumentCaptor.forClass(NetworkCallback.class);
+    private final ArgumentCaptor<BroadcastReceiver> mScreenBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+    private final ArgumentCaptor<BroadcastReceiver> mWifiStateBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+    private static final int TEST_NETWORK_ID = 1;
+    private static final int TEST_24G_FREQUENCY = 2412;
+    private static final int TEST_5G_FREQUENCY = 5745;
+    private long mTimeOffsetMs = 0;
+
+    private class InstantWifiSpy extends InstantWifi {
+        InstantWifiSpy(Context context, AlarmManager alarmManager, Handler handler) {
+            super(context, alarmManager, handler);
+        }
+
+        @Override
+        protected long getMockableElapsedRealtime() {
+            return mTimeOffsetMs;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+
+        mTestAlarmManager = new TestAlarmManager();
+        mAlarmManager = mTestAlarmManager.getAlarmManager();
+        when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+        when(mContext.getSystemServiceName(WifiManager.class)).thenReturn(Context.WIFI_SERVICE);
+        when(mContext.getSystemService(WifiManager.class)).thenReturn(mMockWifiManager);
+        when(mContext.getSystemServiceName(ConnectivityManager.class))
+                .thenReturn(Context.CONNECTIVITY_SERVICE);
+        when(mContext.getSystemService(ConnectivityManager.class))
+                .thenReturn(mMockConnectivityManager);
+        mMockPowerManager = new PowerManager(mContext, mPowerManagerService,
+                mock(IThermalService.class), mHandler);
+        when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
+        when(mPowerManagerService.isInteractive()).thenReturn(true);
+
+        doReturn(mMockWifiInfo).when(mMockWifiInfo).makeCopy(anyLong());
+        mTimeOffsetMs = 0;
+        mInstantWifi = new InstantWifiSpy(mContext, mAlarmManager, mHandler);
+        verifyInstantWifiInitialization();
+    }
+
+    private void verifyInstantWifiInitialization() {
+        verify(mMockConnectivityManager).registerNetworkCallback(any(),
+                mWifiNetworkCallbackCaptor.capture());
+        verify(mContext).registerReceiver(mScreenBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(Intent.ACTION_SCREEN_ON)
+                                && filter.hasAction(Intent.ACTION_SCREEN_OFF)), eq(null), any());
+
+        verify(mContext).registerReceiver(mWifiStateBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(WifiManager.WIFI_STATE_CHANGED_ACTION)), eq(null), any());
+    }
+
+    private void mockWifiConnectedEvent(int networkId, int connectedFrequency) {
+        // Send wifi connected event
+        NetworkCapabilities mockWifiNetworkCapabilities =
+                new NetworkCapabilities.Builder().setTransportInfo(mMockWifiInfo).build();
+        mMockWifiConfiguration.networkId = networkId;
+        when(mMockWifiManager.getPrivilegedConnectedNetwork()).thenReturn(mMockWifiConfiguration);
+        when(mMockWifiInfo.getFrequency()).thenReturn(connectedFrequency);
+        mWifiNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockWifiNetwork,
+                mockWifiNetworkCapabilities);
+        mLooper.dispatchAll();
+    }
+
+    private void mockWifiOnScreenOnBroadcast(boolean isWifiOn, boolean isScreenOn)
+            throws Exception {
+        // Send Wifi On broadcast
+        Intent wifiOnIntent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        wifiOnIntent.putExtra(WifiManager.EXTRA_WIFI_STATE,
+                isWifiOn ? WifiManager.WIFI_STATE_ENABLED : WifiManager.WIFI_STATE_DISABLED);
+        mWifiStateBroadcastReceiverCaptor.getValue().onReceive(mContext, wifiOnIntent);
+        mLooper.dispatchAll();
+        // Send Screen On broadcast
+        Intent screenOnIntent =
+                new Intent(isScreenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+        mScreenBroadcastReceiverCaptor.getValue().onReceive(mContext, screenOnIntent);
+        mLooper.dispatchAll();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(isWifiOn);
+        when(mPowerManagerService.isInteractive()).thenReturn(isScreenOn);
+    }
+
+    @Test
+    public void testisUsePredictedScanningChannels() throws Exception {
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, false /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(false /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+        // Shift time to make it expired
+        mTimeOffsetMs = 1100;
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+    }
+
+    @Test
+    public void testGetPredictedScanningChannels() throws Exception {
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        // Send wifi connected event on T0
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
+        mTimeOffsetMs += 1000; // T1 = 1000 ms
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID + 1, TEST_5G_FREQUENCY);
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        // isUsePredictedScanningChannels is false since wifi on & screen on is expired
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Override the Wifi On & Screen on time
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
+        mTimeOffsetMs += 7 * 24 * 60 * 60 * 1000; // Make T0 expired
+        // Override the Wifi On & Screen on time
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
+    }
+
+    @Test
+    public void testOverrideFreqsForSingleScanSettings() throws Exception {
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), any());
+        Set<Integer> testFreqs = Set.of(
+                TEST_24G_FREQUENCY, TEST_5G_FREQUENCY);
+        SingleScanSettings testSingleScanSettings = new SingleScanSettings();
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
+                testSingleScanSettings, new HashSet<Integer>());
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
+                testSingleScanSettings, null);
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(null, null);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), any(), any(), any());
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(testSingleScanSettings,
+                testFreqs);
+        verify(mAlarmManager).set(anyInt(), anyLong(), any(), any(), any());
+        Set<Integer> overridedFreqs = new HashSet<Integer>();
+        for (ChannelSettings channel : testSingleScanSettings.channelSettings) {
+            overridedFreqs.add(channel.frequency);
+        }
+        assertEquals(testFreqs, overridedFreqs);
+        alarmListenerCaptor.getValue().onAlarm();
+        verify(mMockWifiManager).startScan();
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 362eb14..f12818f 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -26,6 +27,7 @@
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -37,6 +39,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.net.MacAddress;
@@ -104,6 +107,9 @@
     private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener2;
     @Mock
     private Context mContext;
+    @Mock
+    private InstantWifi mMockInstantWifi;
+
     private TestLooper mLooper;
     private TestAlarmManager mTestAlarmManager;
     private AlarmManager mAlarmManager;
@@ -167,6 +173,17 @@
             0x00, 0x00
     };
 
+    private class WifiNl80211ManagerSpy extends WifiNl80211Manager {
+        WifiNl80211ManagerSpy(Context context, IWificond wificond) {
+            super(context, wificond);
+        }
+
+        @Override
+        protected InstantWifi getInstantWifiMockable() {
+            return mMockInstantWifi;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         // Setup mocks for successful WificondControl operation. Failure case mocks should be
@@ -181,6 +198,8 @@
         mLooper = new TestLooper();
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
 
+        doNothing().when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
+                any(), any());
         when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
@@ -189,7 +208,7 @@
         when(mWificond.tearDownApInterface(any())).thenReturn(true);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-        mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+        mWificondControl = new WifiNl80211ManagerSpy(mContext, mWificond);
         mWificondEventHandler = mWificondControl.getWificondEventHandler();
         assertEquals(true,
                 mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
@@ -1159,6 +1178,40 @@
         verify(mWificond).notifyCountryCodeChanged();
     }
 
+    @Test
+    public void testInstantWifi() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(SingleScanSettings settings, Set<Integer> freqs) {
+                if (settings.channelSettings == null) {
+                    settings.channelSettings = new ArrayList<>();
+                } else {
+                    settings.channelSettings.clear();
+                }
+                for (int freq : freqs) {
+                    if (freq > 0) {
+                        ChannelSettings channel = new ChannelSettings();
+                        channel.frequency = freq;
+                        settings.channelSettings.add(channel);
+                    }
+                }
+            }
+        }).when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
+                any(), any());
+        Set<Integer> testPredictedChannelsSet = Set.of(2412, 5745);
+        assertNotEquals(testPredictedChannelsSet, SCAN_FREQ_SET);
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        when(mMockInstantWifi.getPredictedScanningChannels()).thenReturn(testPredictedChannelsSet);
+
+        // Trigger scan to check scan settings are changed
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mMockInstantWifi).getPredictedScanningChannels();
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                testPredictedChannelsSet, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
+    }
+
     // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
     // matches the provided frequency set and ssid set.
     private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {