Merge "Log system keys and shortcuts" into udc-qpr-dev
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 70b72c8..b99996ff 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -433,7 +433,7 @@
     @BinderThread
     @Override
     public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver) {
+            @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
                 flags, showInputToken, resultReceiver, statsToken));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index e472a40..44fed67 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -606,6 +606,7 @@
     InputConnection mStartedInputConnection;
     EditorInfo mInputEditorInfo;
 
+    @InputMethod.ShowFlags
     int mShowInputFlags;
     boolean mShowInputRequested;
     boolean mLastShowInputRequested;
@@ -930,8 +931,9 @@
          */
         @MainThread
         @Override
-        public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
+        public void showSoftInputWithToken(@InputMethod.ShowFlags int flags,
+                ResultReceiver resultReceiver, IBinder showInputToken,
+                @Nullable ImeTracker.Token statsToken) {
             mSystemCallingShowSoftInput = true;
             mCurShowInputToken = showInputToken;
             mCurStatsToken = statsToken;
@@ -949,7 +951,7 @@
          */
         @MainThread
         @Override
-        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+        public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
             ImeTracker.forLogging().onProgress(
                     mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "showSoftInput()");
@@ -1325,7 +1327,8 @@
          * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
          */
         @Deprecated
-        public void toggleSoftInput(int showFlags, int hideFlags) {
+        public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+                @InputMethodManager.HideFlags int hideFlags) {
             InputMethodService.this.onToggleSoftInput(showFlags, hideFlags);
         }
 
@@ -2797,18 +2800,16 @@
      * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()},
      * and the current configuration to decide whether the input view should
      * be shown at this point.
-     * 
-     * @param flags Provides additional information about the show request,
-     * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+     *
      * @param configChange This is true if we are re-showing due to a
      * configuration change.
      * @return Returns true to indicate that the window should be shown.
      */
-    public boolean onShowInputRequested(int flags, boolean configChange) {
+    public boolean onShowInputRequested(@InputMethod.ShowFlags int flags, boolean configChange) {
         if (!onEvaluateInputViewShown()) {
             return false;
         }
-        if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
+        if ((flags & InputMethod.SHOW_EXPLICIT) == 0) {
             if (!configChange && onEvaluateFullscreenMode() && !isInputViewShown()) {
                 // Don't show if this is not explicitly requested by the user and
                 // the input method is fullscreen unless it is already shown. That
@@ -2834,14 +2835,14 @@
      * exposed to IME authors as an overridable public method without {@code @CallSuper}, we have
      * to have this method to ensure that those internal states are always updated no matter how
      * {@link #onShowInputRequested(int, boolean)} is overridden by the IME author.
-     * @param flags Provides additional information about the show request,
-     * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+     *
      * @param configChange This is true if we are re-showing due to a
      * configuration change.
      * @return Returns true to indicate that the window should be shown.
      * @see #onShowInputRequested(int, boolean)
      */
-    private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {
+    private boolean dispatchOnShowInputRequested(@InputMethod.ShowFlags int flags,
+            boolean configChange) {
         final boolean result = onShowInputRequested(flags, configChange);
         mInlineSuggestionSessionController.notifyOnShowInputRequested(result);
         if (result) {
@@ -3274,16 +3275,13 @@
      *
      * The input method will continue running, but the user can no longer use it to generate input
      * by touching the screen.
-     *
-     * @see InputMethodManager#HIDE_IMPLICIT_ONLY
-     * @see InputMethodManager#HIDE_NOT_ALWAYS
-     * @param flags Provides additional operating flags.
      */
-    public void requestHideSelf(int flags) {
+    public void requestHideSelf(@InputMethodManager.HideFlags int flags) {
         requestHideSelf(flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME);
     }
 
-    private void requestHideSelf(int flags, @SoftInputShowHideReason int reason) {
+    private void requestHideSelf(@InputMethodManager.HideFlags int flags,
+            @SoftInputShowHideReason int reason) {
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper,
                 null /* icProto */);
         mPrivOps.hideMySoftInput(flags, reason);
@@ -3292,12 +3290,8 @@
     /**
      * Show the input method's soft input area, so the user sees the input method window and can
      * interact with it.
-     *
-     * @see InputMethodManager#SHOW_IMPLICIT
-     * @see InputMethodManager#SHOW_FORCED
-     * @param flags Provides additional operating flags.
      */
-    public final void requestShowSelf(int flags) {
+    public final void requestShowSelf(@InputMethodManager.ShowFlags int flags) {
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", mDumper,
                 null /* icProto */);
         mPrivOps.showMySoftInput(flags);
@@ -3457,7 +3451,8 @@
     /**
      * Handle a request by the system to toggle the soft input area.
      */
-    private void onToggleSoftInput(int showFlags, int hideFlags) {
+    private void onToggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+            @InputMethodManager.HideFlags int hideFlags) {
         if (DEBUG) Log.v(TAG, "toggleSoftInput()");
         if (isInputViewShown()) {
             requestHideSelf(
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index ce2c180..467daa0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -295,8 +295,8 @@
 
     @AnyThread
     static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
-            @Nullable ResultReceiver resultReceiver,
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -312,7 +312,7 @@
 
     @AnyThread
     static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags,
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
             @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 92380ed..9340f46 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -17,6 +17,7 @@
 package android.view.inputmethod;
 
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,6 +37,8 @@
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -269,6 +272,14 @@
      */
     @MainThread
     public void revokeSession(InputMethodSession session);
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+            SHOW_EXPLICIT,
+            SHOW_FORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ShowFlags {}
     
     /**
      * Flag for {@link #showSoftInput}: this show has been explicitly
@@ -288,8 +299,6 @@
     /**
      * Request that any soft input part of the input method be shown to the user.
      *
-     * @param flags Provides additional information about the show request.
-     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
      * The result code should be
@@ -304,7 +313,7 @@
      * @hide
      */
     @MainThread
-    public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+    public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver,
             IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
         showSoftInput(flags, resultReceiver);
     }
@@ -312,8 +321,6 @@
     /**
      * Request that any soft input part of the input method be shown to the user.
      * 
-     * @param flags Provides additional information about the show request.
-     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
      * The result code should be
@@ -323,11 +330,12 @@
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
     @MainThread
-    public void showSoftInput(int flags, ResultReceiver resultReceiver);
+    public void showSoftInput(@ShowFlags int flags, ResultReceiver resultReceiver);
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
-     * @param flags Provides additional information about the show request.
+     *
+     * @param flags Provides additional information about the hide request.
      * Currently always 0.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
@@ -350,7 +358,8 @@
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
-     * @param flags Provides additional information about the show request.
+     *
+     * @param flags Provides additional information about the hide request.
      * Currently always 0.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index b323314..df9c268 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -39,6 +39,7 @@
 import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
@@ -122,6 +123,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.Collections;
@@ -2034,6 +2037,14 @@
         }
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+            SHOW_IMPLICIT,
+            SHOW_FORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShowFlags {}
+
     /**
      * Flag for {@link #showSoftInput} to indicate that this is an implicit
      * request to show the input window, not as the result of a direct request
@@ -2065,10 +2076,8 @@
      *             {@link View#isFocused view focus}, and its containing window has
      *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
      *             returns {@code false}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      */
-    public boolean showSoftInput(View view, int flags) {
+    public boolean showSoftInput(View view, @ShowFlags int flags) {
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
@@ -2131,21 +2140,20 @@
      *             {@link View#isFocused view focus}, and its containing window has
      *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
      *             returns {@code false}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      * @param resultReceiver If non-null, this will be called by the IME when
      * it has processed your request to tell you what it has done.  The result
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
      */
-    public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
+    public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
         return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
                 SoftInputShowHideReason.SHOW_SOFT_INPUT);
     }
 
-    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken,
+            @ShowFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         if (statsToken == null) {
             statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
@@ -2199,7 +2207,7 @@
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
-    public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
+    public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
             final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
                     null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
@@ -2230,6 +2238,14 @@
         }
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "HIDE_" }, value = {
+            HIDE_IMPLICIT_ONLY,
+            HIDE_NOT_ALWAYS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HideFlags {}
+
     /**
      * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
      * to indicate that the soft input window should only be hidden if it was not explicitly shown
@@ -2251,10 +2267,8 @@
      *
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
      */
-    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) {
         return hideSoftInputFromWindow(windowToken, flags, null);
     }
 
@@ -2276,21 +2290,19 @@
      *
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
      * @param resultReceiver If non-null, this will be called by the IME when
      * it has processed your request to tell you what it has done.  The result
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
      */
-    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
             ResultReceiver resultReceiver) {
         return hideSoftInputFromWindow(windowToken, flags, resultReceiver,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT);
     }
 
-    private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+    private boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
                 null /* component */, Process.myUid(),
@@ -2493,12 +2505,6 @@
      * If not the input window will be displayed.
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #SHOW_IMPLICIT},
-     * {@link #SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Use {@link #showSoftInput(View, int)} or
      * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2507,7 +2513,8 @@
      * has an effect if the calling app is the current IME focus.
      */
     @Deprecated
-    public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+    public void toggleSoftInputFromWindow(IBinder windowToken, @ShowFlags int showFlags,
+            @HideFlags int hideFlags) {
         ImeTracing.getInstance().triggerClientDump(
                 "InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this,
                 null /* icProto */);
@@ -2525,12 +2532,6 @@
      *
      * If the input window is already displayed, it gets hidden.
      * If not the input window will be displayed.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #SHOW_IMPLICIT},
-     * {@link #SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Use {@link #showSoftInput(View, int)} or
      * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2539,7 +2540,7 @@
      * has an effect if the calling app is the current IME focus.
      */
     @Deprecated
-    public void toggleSoftInput(int showFlags, int hideFlags) {
+    public void toggleSoftInput(@ShowFlags int showFlags, @HideFlags int hideFlags) {
         ImeTracing.getInstance().triggerClientDump(
                 "InputMethodManager#toggleSoftInput", InputMethodManager.this,
                 null /* icProto */);
@@ -3552,15 +3553,12 @@
      * @param token Supplies the identifying token given to an input method
      * when it was started, which allows it to perform this operation on
      * itself.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
      * intended for IME developers who should be accessing APIs through the service. APIs in this
      * class are intended for app developers interacting with the IME.
      */
     @Deprecated
-    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
+    public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) {
         InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(
                 flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION);
     }
@@ -3574,15 +3572,12 @@
      * @param token Supplies the identifying token given to an input method
      * when it was started, which allows it to perform this operation on
      * itself.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} or
-     * {@link #SHOW_FORCED} bit set.
      * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
      * intended for IME developers who should be accessing APIs through the service. APIs in this
      * class are intended for app developers interacting with the IME.
      */
     @Deprecated
-    public void showSoftInputFromInputMethod(IBinder token, int flags) {
+    public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) {
         InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags);
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index af6af14..4f48cb6 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -169,12 +169,6 @@
     /**
      * Toggle the soft input window.
      * Applications can toggle the state of the soft input window.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
-     * {@link InputMethodManager#SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link  InputMethodManager#HIDE_IMPLICIT_ONLY},
-     * {@link  InputMethodManager#HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Starting in {@link android.os.Build.VERSION_CODES#S} the system no longer invokes
      * this method, instead it explicitly shows or hides the IME. An {@code InputMethodService}
@@ -182,7 +176,8 @@
      * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
      */
     @Deprecated
-    public void toggleSoftInput(int showFlags, int hideFlags);
+    public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+            @InputMethodManager.HideFlags int hideFlags);
 
     /**
      * This method is called when the cursor and/or the character position relevant to text input
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 81cd280..10336bd 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -66,10 +66,6 @@
         public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
                 releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
 
-        /** Gating the ability for users to dismiss ongoing event notifications */
-        public static final Flag ALLOW_DISMISS_ONGOING =
-                releasedFlag("persist.sysui.notification.ongoing_dismissal");
-
         /** Gating the redaction of OTP notifications on the lockscreen */
         public static final Flag OTP_REDACTION =
                 devFlag("persist.sysui.notification.otp_redaction");
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 66e3333..8a5c7ef 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
@@ -253,13 +254,11 @@
     /**
      * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
      *
-     * @param flags additional operating flags
      * @param reason the reason to hide soft input
-     * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY
-     * @see android.view.inputmethod.InputMethodManager#HIDE_NOT_ALWAYS
      */
     @AnyThread
-    public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason) {
+    public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
+            @SoftInputShowHideReason int reason) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
@@ -275,13 +274,9 @@
 
     /**
      * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)}
-     *
-     * @param flags additional operating flags
-     * @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT
-     * @see android.view.inputmethod.InputMethodManager#SHOW_FORCED
      */
     @AnyThread
-    public void showMySoftInput(int flags) {
+    public void showMySoftInput(@InputMethodManager.ShowFlags int flags) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 66ff01e..39bbac5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2613,6 +2613,17 @@
          assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
     <bool name="config_dismissDreamOnActivityStart">false</bool>
 
+    <!-- Whether to send a user activity event to PowerManager when a dream quits unexpectedly so
+         that the screen won't immediately shut off.
+
+         When a dream stops unexpectedly, such as due to an app update, if the device has been
+         inactive less than the user's screen timeout, the device goes to keyguard and times out
+         back to dreaming after a few seconds. If the device has been inactive longer, the screen
+         will immediately turn off. With this flag on, the device will go back to keyguard in all
+         scenarios rather than turning off, which gives the device a chance to start dreaming
+         again. -->
+    <bool name="config_resetScreenTimeoutOnUnexpectedDreamExit">false</bool>
+
     <!-- The prefixes of dream component names that are loggable.
          Matched against ComponentName#flattenToString() for dream components.
          If empty, logs "other" for all. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bffcf5f..af203d3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2211,6 +2211,7 @@
   <java-symbol type="array" name="config_supportedDreamComplications" />
   <java-symbol type="array" name="config_disabledDreamComponents" />
   <java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
+  <java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" />
   <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
   <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
   <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
diff --git a/core/tests/coretests/src/android/content/TEST_MAPPING b/core/tests/coretests/src/android/content/TEST_MAPPING
new file mode 100644
index 0000000..bbc2458
--- /dev/null
+++ b/core/tests/coretests/src/android/content/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.content.ContentCaptureOptionsTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING
new file mode 100644
index 0000000..f8beac2
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.view.contentcapture"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING
new file mode 100644
index 0000000..3cd4e17
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.view.contentprotection"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 1acf783..22929c76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -88,7 +88,7 @@
     }
 
     /**
-     * Starts Transition of type TRANSIT_START_MOVE_TO_DESKTOP_MODE
+     * Starts Transition of type TRANSIT_START_DRAG_TO_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
      * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
      *                              to desktop animation
@@ -98,18 +98,18 @@
             @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mMoveToDesktopAnimator = moveToDesktopAnimator;
-        startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+        startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
 
     /**
-     * Starts Transition of type TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+     * Starts Transition of type TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
      * @param onAnimationEndCallback to be called after animation
      */
     public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
-        startTransition(Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, wct,
+        startTransition(Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
 
@@ -124,7 +124,7 @@
             MoveToDesktopAnimator moveToDesktopAnimator,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mMoveToDesktopAnimator = moveToDesktopAnimator;
-        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
+        startTransition(Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
 
@@ -167,7 +167,7 @@
         }
 
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (type == Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
             // to null and we don't require an animation
@@ -194,7 +194,7 @@
         }
 
         Rect endBounds = change.getEndAbsBounds();
-        if (type == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                 && !endBounds.isEmpty()) {
             // This Transition animates a task to freeform bounds after being dragged into freeform
@@ -246,7 +246,7 @@
             return true;
         }
 
-        if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             // This Transition animates a task to fullscreen after being dragged from the status
             // bar and then released back into the status bar area
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index af8ef17..7699b4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -737,12 +737,23 @@
         Intent fillInIntent2 = null;
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
         final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+        final ActivityOptions activityOptions1 = options1 != null
+                ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
+        final ActivityOptions activityOptions2 = options2 != null
+                ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
             if (supportMultiInstancesSplit(packageName1)) {
                 fillInIntent1 = new Intent();
                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 fillInIntent2 = new Intent();
                 fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+                if (shortcutInfo1 != null) {
+                    activityOptions1.setApplyMultipleTaskFlagForShortcut(true);
+                }
+                if (shortcutInfo2 != null) {
+                    activityOptions2.setApplyMultipleTaskFlagForShortcut(true);
+                }
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
                 pendingIntent2 = null;
@@ -754,9 +765,10 @@
                         Toast.LENGTH_SHORT).show();
             }
         }
-        mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
-                pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
-                remoteTransition, instanceId);
+        mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
+                activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
+                activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition,
+                instanceId);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index e52fd00..dc78c9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -407,7 +407,7 @@
                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
                 }
                 // Rotation change of independent non display window container.
-                if (change.getParent() == null
+                if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY)
                         && change.getStartRotation() != change.getEndRotation()) {
                     startRotationAnimation(startTransaction, change, info,
                             ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index a242c72..c22cc6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -186,9 +186,12 @@
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
-        final IRemoteTransition remote = remoteTransition.getRemoteTransition();
+        if (remoteTransition == null) return;
+
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "   Merge into remote: %s",
                 remoteTransition);
+
+        final IRemoteTransition remote = remoteTransition.getRemoteTransition();
         if (remote == null) return;
 
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 4ca383f..e45dacf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -150,18 +150,18 @@
     public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
 
     /** Transition type for starting the move to desktop mode. */
-    public static final int TRANSIT_START_MOVE_TO_DESKTOP_MODE =
+    public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 10;
 
     /** Transition type for finalizing the move to desktop mode. */
-    public static final int TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE =
+    public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 11;
 
     /** Transition type to fullscreen from desktop mode. */
     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
 
     /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
-    public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+    public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 13;
 
     /** Transition type to animate the toggle resize between the max and default desktop sizes. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 80cf96a..bb40fd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -197,8 +197,8 @@
             @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
         if (change.getMode() == WindowManager.TRANSIT_CHANGE
-                && (info.getType() == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
-                || info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+                && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                 || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
                 || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
             mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 885ae38..772d97d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -99,7 +99,7 @@
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
-                .startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+                .startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
                         mEnterDesktopTaskTransitionHandler);
         doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
 
@@ -108,7 +108,7 @@
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
-        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE,
+        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE,
                 change);
 
 
@@ -121,7 +121,7 @@
 
     @Test
     public void testTransitEnterDesktopModeAnimation() throws Throwable {
-        final int transitionType = Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE;
+        final int transitionType = Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
@@ -132,7 +132,7 @@
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
         change.setEndAbsBounds(new Rect(0, 0, 1, 1));
         TransitionInfo info = createTransitionInfo(
-                Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, change);
+                Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, change);
 
         runOnUiThread(() -> {
             try {
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index dea7f03..5ea98c0c 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2615,7 +2615,7 @@
             return;
         }
         auto cryptoInfo =
-                cryptoInfoObj ? NativeCryptoInfo{size} : NativeCryptoInfo{env, cryptoInfoObj};
+                cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{size};
         if (env->ExceptionCheck()) {
             // Creation of cryptoInfo failed. Let the exception bubble up.
             return;
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index e1eb36a..25ac3c9 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -419,12 +419,20 @@
         mDynSystem.remove();
     }
 
+    private boolean isDsuSlotLocked() {
+        // Slot names ending with ".lock" are a customized installation.
+        // We expect the client app to provide custom UI to enter/exit DSU mode.
+        // We will ignore the ACTION_REBOOT_TO_NORMAL command and will not show
+        // notifications in this case.
+        return mDynSystem.getActiveDsuSlot().endsWith(".lock");
+    }
+
     private void executeRebootToNormalCommand() {
         if (!isInDynamicSystem()) {
             Log.e(TAG, "It's already running in normal system.");
             return;
         }
-        if (mDynSystem.getActiveDsuSlot().endsWith(".lock")) {
+        if (isDsuSlotLocked()) {
             Log.e(TAG, "Ignore the reboot intent for a locked DSU slot");
             return;
         }
@@ -449,13 +457,13 @@
     private void executeNotifyIfInUseCommand() {
         switch (getStatus()) {
             case STATUS_IN_USE:
-                if (!mHideNotification) {
+                if (!mHideNotification && !isDsuSlotLocked()) {
                     startForeground(NOTIFICATION_ID,
                             buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
                 }
                 break;
             case STATUS_READY:
-                if (!mHideNotification) {
+                if (!mHideNotification && !isDsuSlotLocked()) {
                     startForeground(NOTIFICATION_ID,
                             buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
                 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 00dd8cc..1a938d6 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -179,7 +179,8 @@
      * Check whether tile has order.
      */
     public boolean hasOrder() {
-        return mMetaData.containsKey(META_DATA_KEY_ORDER)
+        return mMetaData != null
+                && mMetaData.containsKey(META_DATA_KEY_ORDER)
                 && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
     }
 
@@ -204,7 +205,7 @@
         CharSequence title = null;
         ensureMetadataNotStale(context);
         final PackageManager packageManager = context.getPackageManager();
-        if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+        if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
             if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
                 // If has as uri to provide dynamic title, skip loading here. UI will later load
                 // at tile binding time.
@@ -284,10 +285,10 @@
      * Optional key to use for this tile.
      */
     public String getKey(Context context) {
+        ensureMetadataNotStale(context);
         if (!hasKey()) {
             return null;
         }
-        ensureMetadataNotStale(context);
         if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
             return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
         } else {
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml b/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml
new file mode 100644
index 0000000..ddd526a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840L160,840Q143,840 131.5,828.5Q120,817 120,800L120,480L204,240Q210,222 225.5,211Q241,200 260,200L700,200Q719,200 734.5,211Q750,222 756,240L840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760ZM232,400L728,400L686,280L274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml b/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml
new file mode 100644
index 0000000..5e1b184
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M0,800L0,720L80,720L80,120L880,120L880,720L960,720L960,800L0,800ZM400,720L560,720L560,680L400,680L400,720ZM160,600L800,600L800,200L160,200L160,600ZM160,600L160,200L160,200L160,600L160,600Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml b/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml
new file mode 100644
index 0000000..baa793c
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,720L680,240L280,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml b/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml
new file mode 100644
index 0000000..cf67cd9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M120,800Q87,800 63.5,776.5Q40,753 40,720L40,240Q40,207 63.5,183.5Q87,160 120,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,720Q920,753 896.5,776.5Q873,800 840,800L120,800ZM160,240L120,240Q120,240 120,240Q120,240 120,240L120,720Q120,720 120,720Q120,720 120,720L160,720L160,240ZM240,720L720,720L720,240L240,240L240,720ZM800,240L800,720L840,720Q840,720 840,720Q840,720 840,720L840,240Q840,240 840,240Q840,240 840,240L800,240ZM800,240L840,240Q840,240 840,240Q840,240 840,240L840,240Q840,240 840,240Q840,240 840,240L800,240L800,240ZM160,240L160,240L120,240Q120,240 120,240Q120,240 120,240L120,240Q120,240 120,240Q120,240 120,240L160,240Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml b/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml
new file mode 100644
index 0000000..252a0db
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M360,880L306,698Q258,660 229,603Q200,546 200,480Q200,414 229,357Q258,300 306,262L360,80L600,80L654,262Q702,300 731,357Q760,414 760,480Q760,546 731,603Q702,660 654,698L600,880L360,880ZM480,680Q563,680 621.5,621.5Q680,563 680,480Q680,397 621.5,338.5Q563,280 480,280Q397,280 338.5,338.5Q280,397 280,480Q280,563 338.5,621.5Q397,680 480,680ZM404,210Q424,205 442.5,202Q461,199 480,199Q499,199 517.5,202Q536,205 556,210L540,160L420,160L404,210ZM420,800L540,800L556,750Q536,755 517.5,757.5Q499,760 480,760Q461,760 442.5,757.5Q424,755 404,750L420,800ZM404,160L420,160L540,160L556,160Q536,160 517.5,160Q499,160 480,160Q461,160 442.5,160Q424,160 404,160ZM420,800L404,800Q424,800 442.5,800Q461,800 480,800Q499,800 517.5,800Q536,800 556,800L540,800L420,800Z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index afab046..b9a4647 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -27,6 +27,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.Log;
@@ -331,6 +332,22 @@
     }
 
     /**
+     * Returns the Hotspot network icon resource.
+     *
+     * @param deviceType The device type of Hotspot network
+     */
+    public static int getHotspotIconResource(int deviceType) {
+        return switch (deviceType) {
+            case NetworkProviderInfo.DEVICE_TYPE_PHONE -> R.drawable.ic_hotspot_phone;
+            case NetworkProviderInfo.DEVICE_TYPE_TABLET -> R.drawable.ic_hotspot_tablet;
+            case NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> R.drawable.ic_hotspot_laptop;
+            case NetworkProviderInfo.DEVICE_TYPE_WATCH -> R.drawable.ic_hotspot_watch;
+            case NetworkProviderInfo.DEVICE_TYPE_AUTO -> R.drawable.ic_hotspot_auto;
+            default -> R.drawable.ic_hotspot_phone;  // Return phone icon as default.
+        };
+    }
+
+    /**
      * Wrapper the {@link #getInternetIconResource} for testing compatibility.
      */
     public static class InternetIconInjector {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index b60dc6a..5293011 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -35,6 +35,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -201,6 +202,25 @@
     }
 
     @Test
+    public void getHotspotIconResource_deviceTypeUnknown_shouldNotCrash() {
+        WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN);
+    }
+
+    @Test
+    public void getHotspotIconResource_deviceTypeExists_shouldNotNull() {
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_PHONE))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_TABLET))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_LAPTOP))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_WATCH))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_AUTO))
+                .isNotNull();
+    }
+
+    @Test
     public void testInternetIconInjector_getIcon_returnsCorrectValues() {
         WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
 
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml
new file mode 100644
index 0000000..f8d4af5
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorPrimaryFixed"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimaryFixed"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml
new file mode 100644
index 0000000..faba8fc
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnPrimaryFixed"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_outline.xml b/packages/SystemUI/res/color/qs_dialog_btn_outline.xml
new file mode 100644
index 0000000..1adfe5b
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_outline.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorPrimary"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml b/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml
new file mode 100644
index 0000000..5dc994f23
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorOnSurface"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnSurface"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
index 1590daa..50405ca 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
@@ -26,7 +26,7 @@
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="18dp"/>
-            <solid android:color="?androidprv:attr/materialColorPrimaryFixed"/>
+            <solid android:color="@color/qs_dialog_btn_filled_large_background"/>
         </shape>
     </item>
 </ripple>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index b0dc652..9e9533a 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:insetTop="@dimen/dialog_button_vertical_inset"
        android:insetBottom="@dimen/dialog_button_vertical_inset">
     <ripple android:color="?android:attr/colorControlHighlight">
@@ -29,7 +28,7 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="@android:color/transparent"/>
-                <stroke android:color="?androidprv:attr/materialColorPrimary"
+                <stroke android:color="@color/qs_dialog_btn_outline"
                         android:width="1dp"
                 />
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/layout/qs_tile_side_icon.xml b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
index f1b7259..fbcead1 100644
--- a/packages/SystemUI/res/layout/qs_tile_side_icon.xml
+++ b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
@@ -30,12 +30,11 @@
         android:visibility="gone"
     />
 
-    <ImageView
+    <com.android.systemui.qs.tileimpl.ChevronImageView
         android:id="@+id/chevron"
         android:layout_width="@dimen/qs_icon_size"
         android:layout_height="@dimen/qs_icon_size"
         android:src="@*android:drawable/ic_chevron_end"
-        android:autoMirrored="true"
         android:visibility="gone"
         android:importantForAccessibility="no"
     />
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
deleted file mode 100644
index d6c63eb..0000000
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2018, 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.
-*/
--->
-<com.android.systemui.statusbar.StatusBarMobileView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/mobile_combo"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:gravity="center_vertical" >
-
-    <include layout="@layout/status_bar_mobile_signal_group_inner" />
-
-</com.android.systemui.statusbar.StatusBarMobileView>
-
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6b85621..31f40e9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1126,13 +1126,13 @@
 
     <style name="Widget.Dialog.Button.BorderButton">
         <item name="android:background">@drawable/qs_dialog_btn_outline</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">@color/qs_dialog_btn_outline_text</item>
     </style>
 
     <style name="Widget.Dialog.Button.Large">
         <item name="android:background">@drawable/qs_dialog_btn_filled_large</item>
         <item name="android:minHeight">56dp</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryFixed</item>
+        <item name="android:textColor">@color/qs_dialog_btn_filled_large_text</item>
     </style>
 
     <style name="Widget.Dialog.Button.QuickSettings">
diff --git a/packages/SystemUI/shared/res/values-my/bools.xml b/packages/SystemUI/shared/res/values-my/bools.xml
new file mode 100644
index 0000000..bcf4b2d
--- /dev/null
+++ b/packages/SystemUI/shared/res/values-my/bools.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+    <!-- Whether to add padding at the bottom of the complication clock -->
+    <bool name="dream_overlay_complication_clock_bottom_padding">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml
new file mode 100644
index 0000000..4b74ead
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/bools.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+    <!-- Whether to add padding at the bottom of the complication clock -->
+    <bool name="dream_overlay_complication_clock_bottom_padding">false</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
index 5a6f184..bc4879d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
@@ -16,34 +16,51 @@
 package com.android.systemui.shared.shadow
 
 import android.content.Context
+import android.content.res.Resources
+import android.content.res.TypedArray
 import android.graphics.Canvas
 import android.util.AttributeSet
 import android.widget.TextClock
 import com.android.systemui.shared.R
 import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
 import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+import javax.inject.Inject
 import kotlin.math.floor
 
 /** Extension of [TextClock] which draws two shadows on the text (ambient and key shadows) */
+class ResourcesProvider @Inject constructor(private val context: Context) {
+    fun getResources(): Resources {
+        return context.resources
+    }
+
+    fun getBoolean(resourceId: Int): Boolean {
+        return getResources().getBoolean(resourceId)
+    }
+}
+
 class DoubleShadowTextClock
 @JvmOverloads
 constructor(
+    private val resourcesProvider: ResourcesProvider,
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
+    defStyleRes: Int = 0,
+    attributesInput: TypedArray? = null
 ) : TextClock(context, attrs, defStyleAttr, defStyleRes) {
     private val mAmbientShadowInfo: ShadowInfo
     private val mKeyShadowInfo: ShadowInfo
 
     init {
-        val attributes =
-            context.obtainStyledAttributes(
-                attrs,
-                R.styleable.DoubleShadowTextClock,
-                defStyleAttr,
-                defStyleRes
-            )
+        var attributes: TypedArray =
+            attributesInput
+                ?: context.obtainStyledAttributes(
+                    attrs,
+                    R.styleable.DoubleShadowTextClock,
+                    defStyleAttr,
+                    defStyleRes
+                )
+
         try {
             val keyShadowBlur =
                 attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextClock_keyShadowBlur, 0)
@@ -98,18 +115,29 @@
                     0
                 )
             if (removeTextDescent) {
-                setPaddingRelative(
-                    0,
-                    0,
-                    0,
-                    textDescentExtraPadding - floor(paint.fontMetrics.descent.toDouble()).toInt()
-                )
+                val addBottomPaddingToClock =
+                    resourcesProvider.getBoolean(
+                        R.bool.dream_overlay_complication_clock_bottom_padding
+                    )
+                val metrics = paint.fontMetrics
+                val padding =
+                    if (addBottomPaddingToClock) {
+                        textDescentExtraPadding +
+                            floor(metrics.descent.toDouble()).toInt() / paddingDividedOffset
+                    } else {
+                        textDescentExtraPadding - floor(metrics.descent.toDouble()).toInt()
+                    }
+                setPaddingRelative(0, 0, 0, padding)
             }
         } finally {
             attributes.recycle()
         }
     }
 
+    companion object {
+        private val paddingDividedOffset = 2
+    }
+
     public override fun onDraw(canvas: Canvas) {
         applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
index 50e5466..1cb8e43 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
@@ -12,12 +12,9 @@
 ) : FrameLayout(context, attrs) {
     private var drawAlpha: Int = 255
 
-    init {
-        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
-    }
-
     protected override fun onSetAlpha(alpha: Int): Boolean {
-        drawAlpha = alpha
+        // Ignore alpha passed from View, prefer to compute it from set values
+        drawAlpha = (255 * this.alpha * transitionAlpha).toInt()
         return true
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index a6252a3..7585279 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -113,6 +113,7 @@
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusView:");
         pw.println("  mDarkAmount: " + mDarkAmount);
+        pw.println("  visibility: " + getVisibility());
         if (mClockView != null) {
             mClockView.dump(pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6854c97..a04d13b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -37,15 +37,19 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
+import androidx.viewpager.widget.ViewPager;
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -58,14 +62,17 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 
 /**
  * Injectable controller for {@link KeyguardStatusView}.
  */
-public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> {
+public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> implements
+        Dumpable {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "KeyguardStatusViewController";
+    @VisibleForTesting static final String TAG = "KeyguardStatusViewController";
 
     /**
      * Duration to use for the animator when the keyguard status view alignment changes, and a
@@ -87,6 +94,8 @@
 
     private Boolean mStatusViewCentered = true;
 
+    private DumpManager mDumpManager;
+
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
             new TransitionListenerAdapter() {
                 @Override
@@ -112,7 +121,8 @@
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardLogger logger,
             FeatureFlags featureFlags,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            DumpManager dumpManager) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -123,11 +133,13 @@
                 logger.getBuffer());
         mInteractionJankMonitor = interactionJankMonitor;
         mFeatureFlags = featureFlags;
+        mDumpManager = dumpManager;
     }
 
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+        mDumpManager.registerDumpable(this);
     }
 
     @Override
@@ -143,6 +155,13 @@
     }
 
     /**
+     * Called in notificationPanelViewController to avoid leak
+     */
+    public void onDestroy() {
+        mDumpManager.unregisterDumpable(TAG);
+    }
+
+    /**
      * Updates views on doze time tick.
      */
     public void dozeTimeTick() {
@@ -365,6 +384,19 @@
             // Excluding media from the transition on split-shade, as it doesn't transition
             // horizontally properly.
             transition.excludeTarget(R.id.status_view_media_container, true);
+
+            // Exclude smartspace viewpager and its children from the transition.
+            //     - Each step of the transition causes the ViewPager to invoke resize,
+            //     which invokes scrolling to the recalculated position. The scrolling
+            //     actions are congested, resulting in kinky translation, and
+            //     delay in settling to the final position. (http://b/281620564#comment1)
+            //     - Also, the scrolling is unnecessary in the transition. We just want
+            //     the viewpager to stay on the same page.
+            //     - Exclude by Class type instead of resource id, since the resource id
+            //     isn't available for all devices, and probably better to exclude all
+            //     ViewPagers any way.
+            transition.excludeTarget(ViewPager.class, true);
+            transition.excludeChildren(ViewPager.class, true);
         }
 
         transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -397,6 +429,24 @@
                 adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
                 adapter.addTarget(clockView);
                 set.addTransition(adapter);
+
+                if (splitShadeEnabled) {
+                    // Exclude smartspace viewpager and its children from the transition set.
+                    //     - This is necessary in addition to excluding them from the
+                    //     ChangeBounds child transition.
+                    //     - Without this, the viewpager is scrolled to the new position
+                    //     (corresponding to its end size) before the size change is realized.
+                    //     Note that the size change is realized at the end of the ChangeBounds
+                    //     transition. With the "prescrolling", the viewpager ends up in a weird
+                    //     position, then recovers smoothly during the transition, and ends at
+                    //     the position for the current page.
+                    //     - Exclude by Class type instead of resource id, since the resource id
+                    //     isn't available for all devices, and probably better to exclude all
+                    //     ViewPagers any way.
+                    set.excludeTarget(ViewPager.class, true);
+                    set.excludeChildren(ViewPager.class, true);
+                }
+
                 set.addListener(mKeyguardStatusAlignmentTransitionListener);
                 TransitionManager.beginDelayedTransition(notifContainerParent, set);
             }
@@ -408,6 +458,11 @@
         constraintSet.applyTo(notifContainerParent);
     }
 
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        mView.dump(pw, args);
+    }
+
     @VisibleForTesting
     static class SplitShadeTransitionAdapter extends Transition {
         private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft";
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 84f1da0..f5022ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1496,7 +1496,7 @@
 
                 @Override
                 public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) {
-                    handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+                    handleBiometricDetected(status.getUserId(), FACE, status.isStrongBiometric());
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 71f78c3..3990b10 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.Assert;
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
@@ -85,6 +86,7 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
+        Assert.isMainThread();
         PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index e5a4d1a..7ae1443 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -479,10 +479,10 @@
                 failureReason,
                 messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
                 authenticateAfterError = modalities.hasFingerprint,
-                suppressIf = { currentMessage ->
+                suppressIf = { currentMessage, history ->
                     modalities.hasFaceAndFingerprint &&
                         failedModality == BiometricModality.Face &&
-                        currentMessage.isError
+                        (currentMessage.isError || history.faceFailed)
                 },
                 failedModality = failedModality,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt
new file mode 100644
index 0000000..d002bf0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import com.android.systemui.biometrics.shared.model.BiometricModality
+
+/** Contains metadata about key events that have occurred while biometric prompt is showing. */
+interface PromptHistory {
+
+    /** If face authentication has failed at least once. */
+    val faceFailed: Boolean
+
+    /** If fingerprint authentication has failed at least once. */
+    val fingerprintFailed: Boolean
+}
+
+class PromptHistoryImpl : PromptHistory {
+    private var failures = mutableSetOf<BiometricModality>()
+
+    override val faceFailed
+        get() = failures.contains(BiometricModality.Face)
+
+    override val fingerprintFailed
+        get() = failures.contains(BiometricModality.Fingerprint)
+
+    /** Record a failure event. */
+    fun failure(modality: BiometricModality) {
+        if (modality != BiometricModality.None) {
+            failures.add(modality)
+        }
+    }
+}
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 8a2e405..dca19c5 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
@@ -204,6 +204,7 @@
             }
             .distinctUntilChanged()
 
+    private val history = PromptHistoryImpl()
     private var messageJob: Job? = null
 
     /**
@@ -214,13 +215,13 @@
      * is set (or via [showHelp] when not set) after the error is dismissed.
      *
      * The error is ignored if the user has already authenticated or if [suppressIf] is true given
-     * the currently showing [PromptMessage].
+     * the currently showing [PromptMessage] and [PromptHistory].
      */
     suspend fun showTemporaryError(
         message: String,
         messageAfterError: String,
         authenticateAfterError: Boolean,
-        suppressIf: (PromptMessage) -> Boolean = { false },
+        suppressIf: (PromptMessage, PromptHistory) -> Boolean = { _, _ -> false },
         hapticFeedback: Boolean = true,
         failedModality: BiometricModality = BiometricModality.None,
     ) = coroutineScope {
@@ -230,7 +231,9 @@
 
         _canTryAgainNow.value = supportsRetry(failedModality)
 
-        if (suppressIf(_message.value)) {
+        val suppress = suppressIf(_message.value, history)
+        history.failure(failedModality)
+        if (suppress) {
             return@coroutineScope
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index d82bf58..6fdb4ca 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -223,10 +223,10 @@
     Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
 
     /** */
-    Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
+    MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli();
 
     /** */
-    Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
+    NearbyMediaDevicesManager getNearbyMediaDevicesManager();
 
     /**
      * Returns {@link CoreStartable}s that should be started with the application.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
index 0e22406..f3a07fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
@@ -16,15 +16,52 @@
 
 package com.android.systemui.dreams
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.DreamLog
-import javax.inject.Inject
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
 
 /** Logs dream-related stuff to a {@link LogBuffer}. */
-class DreamLogger @Inject constructor(@DreamLog private val buffer: LogBuffer) {
-    /** Logs a debug message to the buffer. */
-    fun d(tag: String, message: String) {
-        buffer.log(tag, LogLevel.DEBUG, { str1 = message }, { message })
-    }
+class DreamLogger(buffer: MessageBuffer, tag: String) : Logger(buffer, tag) {
+    fun logDreamOverlayEnabled(enabled: Boolean) =
+        d({ "Dream overlay enabled: $bool1" }) { bool1 = enabled }
+
+    fun logIgnoreAddComplication(reason: String, complication: String) =
+        d({ "Ignore adding complication, reason: $str1, complication: $str2" }) {
+            str1 = reason
+            str2 = complication
+        }
+
+    fun logIgnoreRemoveComplication(reason: String, complication: String) =
+        d({ "Ignore removing complication, reason: $str1, complication: $str2" }) {
+            str1 = reason
+            str2 = complication
+        }
+
+    fun logAddComplication(complication: String) =
+        d({ "Add dream complication: $str1" }) { str1 = complication }
+
+    fun logRemoveComplication(complication: String) =
+        d({ "Remove dream complication: $str1" }) { str1 = complication }
+
+    fun logOverlayActive(active: Boolean) = d({ "Dream overlay active: $bool1" }) { bool1 = active }
+
+    fun logLowLightActive(active: Boolean) =
+        d({ "Low light mode active: $bool1" }) { bool1 = active }
+
+    fun logHasAssistantAttention(hasAttention: Boolean) =
+        d({ "Dream overlay has Assistant attention: $bool1" }) { bool1 = hasAttention }
+
+    fun logStatusBarVisible(visible: Boolean) =
+        d({ "Dream overlay status bar visible: $bool1" }) { bool1 = visible }
+
+    fun logAvailableComplicationTypes(types: Int) =
+        d({ "Available complication types: $int1" }) { int1 = types }
+
+    fun logShouldShowComplications(showComplications: Boolean) =
+        d({ "Dream overlay should show complications: $bool1" }) { bool1 = showComplications }
+
+    fun logShowOrHideStatusBarItem(show: Boolean, type: String) =
+        d({ "${if (bool1) "Showing" else "Hiding"} dream status bar item: $int1" }) {
+            bool1 = show
+            str1 = type
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 484bf3d..01fb522 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -36,6 +36,9 @@
 import com.android.systemui.dreams.dagger.DreamOverlayModule
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.DreamLog
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -65,12 +68,14 @@
     private val mDreamInTranslationYDistance: Int,
     @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
     private val mDreamInTranslationYDurationMs: Long,
-    private val mLogger: DreamLogger,
+    @DreamLog logBuffer: LogBuffer,
 ) {
     companion object {
         private const val TAG = "DreamOverlayAnimationsController"
     }
 
+    private val logger = Logger(logBuffer, TAG)
+
     private var mAnimator: Animator? = null
     private lateinit var view: View
 
@@ -179,11 +184,11 @@
                 doOnEnd {
                     mAnimator = null
                     mOverlayStateController.setEntryAnimationsFinished(true)
-                    mLogger.d(TAG, "Dream overlay entry animations finished.")
+                    logger.d("Dream overlay entry animations finished.")
                 }
-                doOnCancel { mLogger.d(TAG, "Dream overlay entry animations canceled.") }
+                doOnCancel { logger.d("Dream overlay entry animations canceled.") }
                 start()
-                mLogger.d(TAG, "Dream overlay entry animations started.")
+                logger.d("Dream overlay entry animations started.")
             }
     }
 
@@ -242,11 +247,11 @@
                 doOnEnd {
                     mAnimator = null
                     mOverlayStateController.setExitAnimationsRunning(false)
-                    mLogger.d(TAG, "Dream overlay exit animations finished.")
+                    logger.d("Dream overlay exit animations finished.")
                 }
-                doOnCancel { mLogger.d(TAG, "Dream overlay exit animations canceled.") }
+                doOnCancel { logger.d("Dream overlay exit animations canceled.") }
                 start()
-                mLogger.d(TAG, "Dream overlay exit animations started.")
+                logger.d("Dream overlay exit animations started.")
             }
         mOverlayStateController.setExitAnimationsRunning(true)
         return mAnimator as AnimatorSet
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index c2421dc..c9748f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -28,6 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import java.util.ArrayList;
@@ -115,10 +117,10 @@
     public DreamOverlayStateController(@Main Executor executor,
             @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
             FeatureFlags featureFlags,
-            DreamLogger dreamLogger) {
+            @DreamLog LogBuffer logBuffer) {
         mExecutor = executor;
         mOverlayEnabled = overlayEnabled;
-        mLogger = dreamLogger;
+        mLogger = new DreamLogger(logBuffer, TAG);
         mFeatureFlags = featureFlags;
         if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -126,7 +128,7 @@
         } else {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
         }
-        mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled);
+        mLogger.logDreamOverlayEnabled(mOverlayEnabled);
     }
 
     /**
@@ -134,14 +136,13 @@
      */
     public void addComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            mLogger.d(TAG,
-                    "Ignoring adding complication due to overlay disabled: " + complication);
+            mLogger.logIgnoreAddComplication("overlay disabled", complication.toString());
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
-                mLogger.d(TAG, "Added dream complication: " + complication);
+                mLogger.logAddComplication(complication.toString());
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -152,14 +153,13 @@
      */
     public void removeComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            mLogger.d(TAG,
-                    "Ignoring removing complication due to overlay disabled: " + complication);
+            mLogger.logIgnoreRemoveComplication("overlay disabled", complication.toString());
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
-                mLogger.d(TAG, "Removed dream complication: " + complication);
+                mLogger.logRemoveComplication(complication.toString());
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -305,7 +305,7 @@
      * @param active {@code true} if overlay is active, {@code false} otherwise.
      */
     public void setOverlayActive(boolean active) {
-        mLogger.d(TAG, "Dream overlay active: " + active);
+        mLogger.logOverlayActive(active);
         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
     }
 
@@ -314,7 +314,7 @@
      * @param active {@code true} if low light mode is active, {@code false} otherwise.
      */
     public void setLowLightActive(boolean active) {
-        mLogger.d(TAG, "Low light mode active: " + active);
+        mLogger.logLowLightActive(active);
 
         if (isLowLightActive() && !active) {
             // Notify that we're exiting low light only on the transition from active to not active.
@@ -346,7 +346,7 @@
      * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
      */
     public void setHasAssistantAttention(boolean hasAttention) {
-        mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention);
+        mLogger.logHasAssistantAttention(hasAttention);
         modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
     }
 
@@ -355,7 +355,7 @@
      * @param visible {@code true} if the status bar is visible, {@code false} otherwise.
      */
     public void setDreamOverlayStatusBarVisible(boolean visible) {
-        mLogger.d(TAG, "Dream overlay status bar visible: " + visible);
+        mLogger.logStatusBarVisible(visible);
         modifyState(
                 visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
     }
@@ -373,7 +373,7 @@
      */
     public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
         mExecutor.execute(() -> {
-            mLogger.d(TAG, "Available complication types: " + types);
+            mLogger.logAvailableComplicationTypes(types);
             mAvailableComplicationTypes = types;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
@@ -391,7 +391,7 @@
      */
     public void setShouldShowComplications(boolean shouldShowComplications) {
         mExecutor.execute(() -> {
-            mLogger.d(TAG, "Should show complications: " + shouldShowComplications);
+            mLogger.logShouldShowComplications(shouldShowComplications);
             mShouldShowComplications = shouldShowComplications;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 3a28408..a6401b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -36,6 +36,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -161,7 +163,7 @@
             DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
             DreamOverlayStateController dreamOverlayStateController,
             UserTracker userTracker,
-            DreamLogger dreamLogger) {
+            @DreamLog LogBuffer logBuffer) {
         super(view);
         mResources = resources;
         mMainExecutor = mainExecutor;
@@ -177,7 +179,7 @@
         mZenModeController = zenModeController;
         mDreamOverlayStateController = dreamOverlayStateController;
         mUserTracker = userTracker;
-        mLogger = dreamLogger;
+        mLogger = new DreamLogger(logBuffer, TAG);
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -346,8 +348,8 @@
             @Nullable String contentDescription) {
         mMainExecutor.execute(() -> {
             if (mIsAttached) {
-                mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: "
-                        + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
+                mLogger.logShowOrHideStatusBarItem(
+                        show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
                 mView.showIcon(iconType, show, contentDescription);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0670ec3..efa5981 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -254,7 +254,7 @@
     /** Migrate the indication area to the new keyguard root view. */
     // TODO(b/280067944): Tracking bug.
     @JvmField
-    val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true)
+    val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area")
 
     /**
      * Migrate the bottom area to the new keyguard root view.
@@ -294,6 +294,11 @@
     @JvmField
     val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl")
 
+    /** Migrate the status view from the notification panel to keyguard root view. */
+    // TODO(b/291767565): Tracking bug.
+    @JvmField
+    val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -362,16 +367,6 @@
 
     // 600- status bar
 
-    // TODO(b/256614753): Tracking Bug
-    val NEW_STATUS_BAR_MOBILE_ICONS = releasedFlag(606, "new_status_bar_mobile_icons")
-
-    // TODO(b/256614751): Tracking Bug
-    val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
-        unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true)
-
-    // TODO(b/260881289): Tracking Bug
-    val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
-        unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
 
     // TODO(b/265892345): Tracking Bug
     val PLUG_IN_STATUS_BAR_CHIP = releasedFlag(265892345, "plug_in_status_bar_chip")
@@ -411,12 +406,6 @@
     // TODO(b/254512502): Tracking Bug
     val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
 
-    // TODO(b/254512726): Tracking Bug
-    val MEDIA_NEARBY_DEVICES = releasedFlag(903, "media_nearby_devices")
-
-    // TODO(b/254512695): Tracking Bug
-    val MEDIA_MUTE_AWAIT = releasedFlag(904, "media_mute_await")
-
     // TODO(b/254512654): Tracking Bug
     @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag(905, "dream_media_complication")
 
@@ -432,9 +421,6 @@
     // TODO(b/263272731): Tracking Bug
     val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
 
-    // TODO(b/265813373): Tracking Bug
-    val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
-
     // TODO(b/266157412): Tracking Bug
     val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
 
@@ -780,7 +766,8 @@
 
     // TODO(b/285174336): Tracking Bug
     @JvmField
-    val USE_REPOS_FOR_BOUNCER_SHOWING = unreleasedFlag(2900, "use_repos_for_bouncer_showing")
+    val USE_REPOS_FOR_BOUNCER_SHOWING =
+        unreleasedFlag(2900, "use_repos_for_bouncer_showing", teamfood = true)
 
     // 3100 - Haptic interactions
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
new file mode 100644
index 0000000..eaecda5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.flags
+
+import android.util.Log
+import com.android.systemui.Dependency
+
+/**
+ * This class promotes best practices for flag guarding System UI view refactors.
+ * * [isEnabled] allows changing an implementation.
+ * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and
+ *   ensure that it is not being invoked accidentally in the post-flag refactor.
+ * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on
+ *   flag-disabled builds, but with a check that should crash eng builds or tests when the
+ *   expectation is violated.
+ *
+ * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it,
+ * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes
+ * inside views where injecting flag values after initialization can be error-prone.
+ */
+class ViewRefactorFlag
+private constructor(
+    private val injectedFlags: FeatureFlags?,
+    private val flag: BooleanFlag,
+    private val readFlagValue: (FeatureFlags) -> Boolean
+) {
+    @JvmOverloads
+    constructor(
+        flags: FeatureFlags? = null,
+        flag: UnreleasedFlag
+    ) : this(flags, flag, { it.isEnabled(flag) })
+
+    @JvmOverloads
+    constructor(
+        flags: FeatureFlags? = null,
+        flag: ReleasedFlag
+    ) : this(flags, flag, { it.isEnabled(flag) })
+
+    /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */
+    val isEnabled by lazy {
+        @Suppress("DEPRECATION")
+        val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java)
+        readFlagValue(featureFlags)
+    }
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setController(NotificationShelfController notificationShelfController) {
+     *     mShelfRefactor.assertDisabled();
+     *     mController = notificationShelfController;
+     * }
+     * ````
+     */
+    fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." }
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setShelfIcons(NotificationIconContainer icons) {
+     *     if (mShelfRefactor.expectEnabled()) {
+     *         mShelfIcons = icons;
+     *     }
+     * }
+     * ```
+     */
+    fun expectEnabled(): Boolean {
+        if (!isEnabled) {
+            val message = "Code path not supported when $flag is disabled."
+            Log.wtf(TAG, message, Exception(message))
+        }
+        return isEnabled
+    }
+
+    private companion object {
+        private const val TAG = "ViewRefactorFlag"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 23f6fa6..03a270e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -25,19 +25,24 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
 import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /** Binds keyguard views on startup, and also exposes methods to allow rebinding if views change */
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class KeyguardViewConfigurator
 @Inject
@@ -51,11 +56,14 @@
     private val indicationController: KeyguardIndicationController,
     private val keyguardLayoutManager: KeyguardLayoutManager,
     private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
+    private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
+    private val chipbarCoordinator: ChipbarCoordinator,
 ) : CoreStartable {
 
     private var indicationAreaHandle: DisposableHandle? = null
 
     override fun start() {
+        bindKeyguardRootView()
         val notificationPanel =
             notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
         bindIndicationArea(notificationPanel)
@@ -116,4 +124,13 @@
             }
         }
     }
+
+    private fun bindKeyguardRootView() {
+        KeyguardRootViewBinder.bind(
+            keyguardRootView,
+            featureFlags,
+            occludingAppDeviceEntryMessageViewModel,
+            chipbarCoordinator,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 86cd962..80a9234 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2976,6 +2976,7 @@
     }
 
     private void onKeyguardExitFinished() {
+        if (DEBUG) Log.d(TAG, "onKeyguardExitFinished()");
         // only play "unlock" noises if not on a call (since the incall UI
         // disables the keyguard)
         if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
@@ -3197,13 +3198,14 @@
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
-            if (mPowerGestureIntercepted) {
+            if (mPowerGestureIntercepted && mOccluded && isSecure()) {
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
             if (DEBUG) {
                 Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded
                         + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons
+                        + " mPowerGestureIntercepted=" + mPowerGestureIntercepted
                         +  " --> flags=0x" + Integer.toHexString(flags));
             }
 
@@ -3431,6 +3433,7 @@
         pw.print("  mPendingLock: "); pw.println(mPendingLock);
         pw.print("  wakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
         pw.print("  mPendingPinLock: "); pw.println(mPendingPinLock);
+        pw.print("  mPowerGestureIntercepted: "); pw.println(mPowerGestureIntercepted);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 6edf40f..d1f011e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -545,11 +545,11 @@
             faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
             return
         }
-        if (_isAuthRunning.value || detectCancellationSignal != null) {
+        if (_isAuthRunning.value) {
             faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
             return
         }
-
+        detectCancellationSignal?.cancel()
         detectCancellationSignal = CancellationSignal()
         withContext(mainDispatcher) {
             // We always want to invoke face detect in the main thread.
@@ -574,6 +574,7 @@
         if (authCancellationSignal == null) return
 
         authCancellationSignal?.cancel()
+        cancelNotReceivedHandlerJob?.cancel()
         cancelNotReceivedHandlerJob =
             applicationScope.launch {
                 delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
@@ -583,6 +584,7 @@
                     cancellationInProgress,
                     faceAuthRequestedWhileCancellation
                 )
+                _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
                 onFaceAuthRequestCompleted()
             }
         cancellationInProgress = true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
new file mode 100644
index 0000000..a2287c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.hardware.fingerprint.FingerprintManager
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+
+/** Business logic for handling authentication events when an app is occluding the lockscreen. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludingAppDeviceEntryInteractor
+@Inject
+constructor(
+    biometricMessageInteractor: BiometricMessageInteractor,
+    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    keyguardInteractor: KeyguardInteractor,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
+    @Application scope: CoroutineScope,
+    private val context: Context,
+    activityStarter: ActivityStarter,
+) {
+    private val keyguardOccludedByApp: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isKeyguardOccluded,
+                keyguardInteractor.isKeyguardShowing,
+                primaryBouncerInteractor.isShowing,
+                alternateBouncerInteractor.isVisible,
+            ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
+                occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
+            }
+            .distinctUntilChanged()
+    private val fingerprintUnlockSuccessEvents: Flow<Unit> =
+        fingerprintAuthRepository.authenticationStatus
+            .ifKeyguardOccludedByApp()
+            .filter { it is SuccessFingerprintAuthenticationStatus }
+            .map {} // maps FingerprintAuthenticationStatus => Unit
+    private val fingerprintLockoutEvents: Flow<Unit> =
+        fingerprintAuthRepository.authenticationStatus
+            .ifKeyguardOccludedByApp()
+            .filter {
+                it is ErrorFingerprintAuthenticationStatus &&
+                    (it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
+                        it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+            }
+            .map {} // maps FingerprintAuthenticationStatus => Unit
+    val message: Flow<BiometricMessage?> =
+        merge(
+                biometricMessageInteractor.fingerprintErrorMessage,
+                biometricMessageInteractor.fingerprintFailMessage,
+                biometricMessageInteractor.fingerprintHelpMessage,
+            )
+            .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
+
+    init {
+        scope.launch {
+            // On fingerprint success, go to the home screen
+            fingerprintUnlockSuccessEvents.collect { goToHomeScreen() }
+        }
+
+        scope.launch {
+            // On device fingerprint lockout, request the bouncer with a runnable to
+            // go to the home screen. Without this, the bouncer won't proceed to the home screen.
+            fingerprintLockoutEvents.collect {
+                activityStarter.dismissKeyguardThenExecute(
+                    object : ActivityStarter.OnDismissAction {
+                        override fun onDismiss(): Boolean {
+                            goToHomeScreen()
+                            return false
+                        }
+
+                        override fun willRunAnimationOnKeyguard(): Boolean {
+                            return false
+                        }
+                    },
+                    /* cancel= */ null,
+                    /* afterKeyguardGone */ false
+                )
+            }
+        }
+    }
+
+    /** Launches an Activity which forces the current app to background by going home. */
+    private fun goToHomeScreen() {
+        context.startActivity(
+            Intent(Intent.ACTION_MAIN).apply {
+                addCategory(Intent.CATEGORY_HOME)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            }
+        )
+    }
+
+    private fun <T> Flow<T>.ifKeyguardOccludedByApp(elseFlow: Flow<T> = emptyFlow()): Flow<T> {
+        return keyguardOccludedByApp.flatMapLatest { keyguardOccludedByApp ->
+            if (keyguardOccludedByApp) {
+                this
+            } else {
+                elseFlow
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index 0f6d82e..d9792cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -23,7 +23,10 @@
  * Authentication status provided by
  * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
  */
-sealed class FaceAuthenticationStatus
+sealed class FaceAuthenticationStatus(
+    // present to break equality check if the same error occurs repeatedly.
+    val createdAt: Long = elapsedRealtime()
+)
 
 /** Success authentication status. */
 data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
@@ -43,8 +46,6 @@
 data class ErrorFaceAuthenticationStatus(
     val msgId: Int,
     val msg: String? = null,
-    // present to break equality check if the same error occurs repeatedly.
-    val createdAt: Long = elapsedRealtime()
 ) : FaceAuthenticationStatus() {
     /**
      * Method that checks if [msgId] is a lockout error. A lockout error means that face
@@ -63,7 +64,21 @@
     fun isHardwareError() =
         msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE ||
             msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS
+
+    companion object {
+        /**
+         * Error message that is created when cancel confirmation is not received from FaceManager
+         * after we request for a cancellation of face auth.
+         */
+        fun cancelNotReceivedError() = ErrorFaceAuthenticationStatus(-1, "")
+    }
 }
 
 /** Face detection success message. */
-data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
+data class FaceDetectionStatus(
+    val sensorId: Int,
+    val userId: Int,
+    val isStrongBiometric: Boolean,
+    // present to break equality check if the same error occurs repeatedly.
+    val createdAt: Long = elapsedRealtime()
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
new file mode 100644
index 0000000..1db596b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.keyguard.ui.binder
+
+import android.annotation.DrawableRes
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.temporarydisplay.ViewPriority
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
+@ExperimentalCoroutinesApi
+object KeyguardRootViewBinder {
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        featureFlags: FeatureFlags,
+        occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
+        chipbarCoordinator: ChipbarCoordinator,
+    ) {
+        if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
+                            ->
+                            if (biometricMessage?.message != null) {
+                                chipbarCoordinator.displayView(
+                                    createChipbarInfo(
+                                        biometricMessage.message,
+                                        R.drawable.ic_lock,
+                                    )
+                                )
+                            } else {
+                                chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
+     */
+    private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
+        return ChipbarInfo(
+            startIcon =
+                TintedIcon(
+                    Icon.Resource(icon, null),
+                    ChipbarInfo.DEFAULT_ICON_TINT,
+                ),
+            text = Text.Loaded(message),
+            endItem = null,
+            vibrationEffect = null,
+            windowTitle = "OccludingAppUnlockMsgChip",
+            wakeReason = "OCCLUDING_APP_UNLOCK_MSG_CHIP",
+            timeoutMs = 3500,
+            id = ID,
+            priority = ViewPriority.CRITICAL,
+            instanceId = null,
+        )
+    }
+
+    private const val ID = "occluding_app_device_entry_unlock_msg"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
new file mode 100644
index 0000000..3a162d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.BiometricMessage
+import com.android.systemui.keyguard.domain.interactor.OccludingAppDeviceEntryInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/** Shows authentication messages over occcluding apps over the lockscreen. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludingAppDeviceEntryMessageViewModel
+@Inject
+constructor(
+    interactor: OccludingAppDeviceEntryInteractor,
+) {
+    val message: Flow<BiometricMessage?> = interactor.message
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 8d3c6d5..8f884d24 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -30,6 +30,9 @@
 import android.os.ResultReceiver
 import android.os.UserHandle
 import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
@@ -54,7 +57,11 @@
     /** This is used to override the dependency in a screenshot test */
     @VisibleForTesting
     private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
+) :
+    ChooserActivity(),
+    MediaProjectionAppSelectorView,
+    MediaProjectionAppSelectorResultHandler,
+    LifecycleOwner {
 
     @Inject
     constructor(
@@ -62,6 +69,8 @@
         activityLauncher: AsyncActivityLauncher
     ) : this(componentFactory, activityLauncher, listControllerFactory = null)
 
+    private val lifecycleRegistry = LifecycleRegistry(this)
+    override val lifecycle = lifecycleRegistry
     private lateinit var configurationController: ConfigurationController
     private lateinit var controller: MediaProjectionAppSelectorController
     private lateinit var recentsViewController: MediaProjectionRecentsViewController
@@ -75,7 +84,9 @@
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
     public override fun onCreate(bundle: Bundle?) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
         component = componentFactory.create(activity = this, view = this, resultHandler = this)
+        component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
 
         // Create a separate configuration controller for this activity as the configuration
         // might be different from the global one
@@ -96,6 +107,26 @@
         controller.init()
     }
 
+    override fun onStart() {
+        super.onStart()
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    }
+
+    override fun onPause() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        super.onPause()
+    }
+
+    override fun onStop() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+        super.onStop()
+    }
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         configurationController.onConfigurationChanged(newConfig)
@@ -152,6 +183,8 @@
     }
 
     override fun onDestroy() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+        component.lifecycleObservers.forEach { lifecycle.removeObserver(it) }
         // onDestroy is also called when an app is selected, in that case we only want to send
         // RECORD_CONTENT_TASK but not RECORD_CANCEL
         if (!taskSelected) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 3fc3ad6..0a5f857 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -144,7 +144,7 @@
         val oldKey: String?,
         val controller: MediaController?,
         val localMediaManager: LocalMediaManager,
-        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
+        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager,
     ) :
         LocalMediaManager.DeviceCallback,
         MediaController.Callback(),
@@ -180,7 +180,7 @@
                 if (!started) {
                     localMediaManager.registerCallback(this)
                     localMediaManager.startScan()
-                    muteAwaitConnectionManager?.startListening()
+                    muteAwaitConnectionManager.startListening()
                     playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
                     playbackVolumeControlId = controller?.playbackInfo?.volumeControlId
                     controller?.registerCallback(this)
@@ -198,7 +198,7 @@
                     controller?.unregisterCallback(this)
                     localMediaManager.stopScan()
                     localMediaManager.unregisterCallback(this)
-                    muteAwaitConnectionManager?.stopListening()
+                    muteAwaitConnectionManager.stopListening()
                     configurationController.removeCallback(configListener)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 09aef88..f2db088 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -34,15 +34,6 @@
         return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
     }
 
-    /** Check whether we support displaying information about mute await connections. */
-    fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
-
-    /**
-     * Check whether we enable support for nearby media devices. See
-     * [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information.
-     */
-    fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
-
     /**
      * If true, keep active media controls for the lifetime of the MediaSession, regardless of
      * whether the underlying notification was dismissed
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 46efac5..888cd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -23,10 +23,7 @@
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.media.controls.ui.MediaHostStatesManager;
-import com.android.systemui.media.controls.util.MediaFlags;
 import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
-import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer;
@@ -119,29 +116,4 @@
         }
         return Optional.of(helperLazy.get());
     }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli(
-            MediaFlags mediaFlags,
-            Lazy<MediaMuteAwaitConnectionCli> muteAwaitConnectionCliLazy
-    ) {
-        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(muteAwaitConnectionCliLazy.get());
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<NearbyMediaDevicesManager> providesNearbyMediaDevicesManager(
-            MediaFlags mediaFlags,
-            Lazy<NearbyMediaDevicesManager> nearbyMediaDevicesManagerLazy) {
-        if (!mediaFlags.areNearbyMediaDevicesEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(nearbyMediaDevicesManagerLazy.get());
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index a1e9995..18d5103 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -46,7 +45,7 @@
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
+    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
     private val keyGuardManager: KeyguardManager,
@@ -62,7 +61,7 @@
 
         val controller = MediaOutputController(context, packageName,
                 mediaSessionManager, lbm, starter, notifCollection,
-                dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+                dialogLaunchAnimator, nearbyMediaDevicesManager, audioManager,
                 powerExemptionManager, keyGuardManager, featureFlags, userTracker)
         val dialog =
                 MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index cc75478..b6ca0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -99,7 +99,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -177,7 +176,7 @@
             lbm, ActivityStarter starter,
             CommonNotifCollection notifCollection,
             DialogLaunchAnimator dialogLaunchAnimator,
-            Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
+            NearbyMediaDevicesManager nearbyMediaDevicesManager,
             AudioManager audioManager,
             PowerExemptionManager powerExemptionManager,
             KeyguardManager keyGuardManager,
@@ -198,7 +197,7 @@
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mNearbyMediaDevicesManager = nearbyMediaDevicesManagerOptional.orElse(null);
+        mNearbyMediaDevicesManager = nearbyMediaDevicesManager;
         mColorItemContent = Utils.getColorStateListDefaultColor(mContext,
                 R.color.media_dialog_item_main_content);
         mColorSeekbarProgress = Utils.getColorStateListDefaultColor(mContext,
@@ -927,7 +926,7 @@
     void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
-                mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
+                mNotifCollection, mDialogLaunchAnimator, mNearbyMediaDevicesManager,
                 mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
                 mUserTracker);
         MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 4c168ec..af65937 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -48,7 +47,7 @@
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
+    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
     private val keyGuardManager: KeyguardManager,
@@ -68,7 +67,7 @@
         val controller = MediaOutputController(
             context, packageName,
             mediaSessionManager, lbm, starter, notifCollection,
-            dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+            dialogLaunchAnimator, nearbyMediaDevicesManager, audioManager,
             powerExemptionManager, keyGuardManager, featureFlags, userTracker)
         val dialog =
             MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller,
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
index e260894..97ec654 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
@@ -21,14 +21,12 @@
 import com.android.settingslib.media.LocalMediaManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.util.MediaFlags
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /** Factory class to create [MediaMuteAwaitConnectionManager] instances. */
 @SysUISingleton
 class MediaMuteAwaitConnectionManagerFactory @Inject constructor(
-    private val mediaFlags: MediaFlags,
     private val context: Context,
     private val logger: MediaMuteAwaitLogger,
     @Main private val mainExecutor: Executor
@@ -36,10 +34,7 @@
     private val deviceIconUtil = DeviceIconUtil()
 
     /** Creates a [MediaMuteAwaitConnectionManager]. */
-    fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? {
-        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
-            return null
-        }
+    fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager {
         return MediaMuteAwaitConnectionManager(
                 mainExecutor, localMediaManager, context, deviceIconUtil, logger
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 60504e4..8a565fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -30,8 +30,4 @@
     /** Check whether the flag for the receiver success state is enabled. */
     fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
         featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
-
-    /** True if the media transfer chip can be dismissed via a gesture. */
-    fun isMediaTttDismissGestureEnabled(): Boolean =
-        featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 3088d8b..11538fa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.os.UserHandle
+import androidx.lifecycle.DefaultLifecycleObserver
 import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
@@ -46,6 +47,7 @@
 import dagger.Subcomponent
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import javax.inject.Qualifier
 import javax.inject.Scope
 import kotlinx.coroutines.CoroutineScope
@@ -100,6 +102,12 @@
     @MediaProjectionAppSelectorScope
     fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
 
+    @Binds
+    @IntoSet
+    fun taskPreviewSizeProviderAsLifecycleObserver(
+        impl: TaskPreviewSizeProvider
+    ): DefaultLifecycleObserver
+
     companion object {
         @Provides
         @MediaProjectionAppSelector
@@ -166,4 +174,5 @@
     @get:PersonalProfile val personalProfileUserHandle: UserHandle
 
     @MediaProjectionAppSelector val configurationController: ConfigurationController
+    val lifecycleObservers: Set<DefaultLifecycleObserver>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 89f66b7..864d35a 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -21,6 +21,8 @@
 import android.graphics.Rect
 import android.view.WindowInsets.Type
 import android.view.WindowManager
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
@@ -35,18 +37,22 @@
 constructor(
     private val context: Context,
     private val windowManager: WindowManager,
-    configurationController: ConfigurationController
-) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener {
+    private val configurationController: ConfigurationController,
+) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener, DefaultLifecycleObserver {
 
     /** Returns the size of the task preview on the screen in pixels */
     val size: Rect = calculateSize()
 
     private val listeners = arrayListOf<TaskPreviewSizeListener>()
 
-    init {
+    override fun onCreate(owner: LifecycleOwner) {
         configurationController.addCallback(this)
     }
 
+    override fun onDestroy(owner: LifecycleOwner) {
+        configurationController.removeCallback(this)
+    }
+
     override fun onConfigChanged(newConfig: Configuration) {
         val newSize = calculateSize()
         if (newSize != size) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt
new file mode 100644
index 0000000..8fd1d6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.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.qs.tileimpl
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+
+class ChevronImageView(context: Context, attrs: AttributeSet?) : ImageView(context, attrs) {
+
+    override fun resolveLayoutDirection(): Boolean {
+        val previousLayoutDirection = layoutDirection
+        return super.resolveLayoutDirection().also { resolved ->
+            if (resolved && layoutDirection != previousLayoutDirection) {
+                onRtlPropertiesChanged(layoutDirection)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 9399d48..202d6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1169,6 +1169,9 @@
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
             FrameLayout userAvatarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView) {
+        if (mKeyguardStatusViewController != null) {
+            mKeyguardStatusViewController.onDestroy();
+        }
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 5d06f8d0..15ff31f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -21,11 +21,12 @@
 
 /**
  * A temporary base class that's shared between our old status bar connectivity view implementations
- * ([StatusBarMobileView]) and our new status bar implementations ([ModernStatusBarWifiView],
- * [ModernStatusBarMobileView]).
+ * and our new status bar implementations ([ModernStatusBarWifiView], [ModernStatusBarMobileView]).
  *
  * Once our refactor is over, we should be able to delete this go-between class and the old view
  * class.
+ *
+ * NOTE: the old classes are now deleted, and this class can be removed.
  */
 abstract class BaseStatusBarFrameLayout
 @JvmOverloads
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
index 4ec5f46..7a989cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
@@ -19,7 +19,6 @@
 import android.view.View;
 
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -52,7 +51,6 @@
         mActivatableNotificationViewController = activatableNotificationViewController;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
-        mView.setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
         mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index fb88a96..763400b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -27,10 +27,12 @@
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Point;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
@@ -41,6 +43,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.Log;
+import android.view.Display;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -74,11 +77,15 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import dagger.Lazy;
 
@@ -138,6 +145,14 @@
     private BackDropView mBackdrop;
     private ImageView mBackdropFront;
     private ImageView mBackdropBack;
+    private final Point mTmpDisplaySize = new Point();
+
+    private final DisplayManager mDisplayManager;
+    @Nullable
+    private List<String> mSmallerInternalDisplayUids;
+    private Display mCurrentDisplay;
+
+    private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable;
 
     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
@@ -184,7 +199,8 @@
             SysuiColorExtractor colorExtractor,
             KeyguardStateController keyguardStateController,
             DumpManager dumpManager,
-            WallpaperManager wallpaperManager) {
+            WallpaperManager wallpaperManager,
+            DisplayManager displayManager) {
         mContext = context;
         mMediaArtworkProcessor = mediaArtworkProcessor;
         mKeyguardBypassController = keyguardBypassController;
@@ -200,6 +216,7 @@
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
         mKeyguardStateController = keyguardStateController;
+        mDisplayManager = displayManager;
         mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled();
 
         setupNotifPipeline();
@@ -477,6 +494,48 @@
     }
 
     /**
+     * Notify lockscreen wallpaper drawable the current internal display.
+     */
+    public void onDisplayUpdated(Display display) {
+        Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
+        mCurrentDisplay = display;
+        if (mWallapperDrawable != null) {
+            mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays());
+        }
+        Trace.endSection();
+    }
+
+    private boolean isOnSmallerInternalDisplays() {
+        if (mSmallerInternalDisplayUids == null) {
+            mSmallerInternalDisplayUids = findSmallerInternalDisplayUids();
+        }
+        return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId());
+    }
+
+    private List<String> findSmallerInternalDisplayUids() {
+        if (mSmallerInternalDisplayUids != null) {
+            return mSmallerInternalDisplayUids;
+        }
+        List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays(
+                        DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED))
+                .filter(display -> display.getType() == Display.TYPE_INTERNAL)
+                .collect(Collectors.toList());
+        if (internalDisplays.isEmpty()) {
+            return List.of();
+        }
+        Display largestDisplay = internalDisplays.stream()
+                .max(Comparator.comparingInt(this::getRealDisplayArea))
+                .orElse(internalDisplays.get(0));
+        internalDisplays.remove(largestDisplay);
+        return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList());
+    }
+
+    private int getRealDisplayArea(Display display) {
+        display.getRealSize(mTmpDisplaySize);
+        return mTmpDisplaySize.x * mTmpDisplaySize.y;
+    }
+
+    /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
@@ -551,7 +610,7 @@
                     mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
             if (lockWallpaper != null) {
                 artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
-                        mBackdropBack.getResources(), lockWallpaper);
+                        mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays());
                 // We're in the SHADE mode on the SIM screen - yet we still need to show
                 // the lockscreen wallpaper in that mode.
                 allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
@@ -611,6 +670,10 @@
                     mBackdropBack.setBackgroundColor(0xFFFFFFFF);
                     mBackdropBack.setImageDrawable(new ColorDrawable(c));
                 } else {
+                    if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) {
+                        mWallapperDrawable =
+                                (LockscreenWallpaper.WallpaperDrawable) artworkDrawable;
+                    }
                     mBackdropBack.setImageDrawable(artworkDrawable);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 25a1dc6..3f37c60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -40,6 +39,8 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -95,8 +96,10 @@
     private float mCornerAnimationDistance;
     private NotificationShelfController mController;
     private float mActualWidth = -1;
-    private boolean mSensitiveRevealAnimEnabled;
-    private boolean mShelfRefactorFlagEnabled;
+    private final ViewRefactorFlag mSensitiveRevealAnim =
+            new ViewRefactorFlag(Flags.SENSITIVE_REVEAL_ANIM);
+    private final ViewRefactorFlag mShelfRefactor =
+            new ViewRefactorFlag(Flags.NOTIFICATION_SHELF_REFACTOR);
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
     private NotificationStackScrollLayout mHostLayout;
@@ -130,7 +133,7 @@
 
     public void bind(AmbientState ambientState,
                      NotificationStackScrollLayoutController hostLayoutController) {
-        assertRefactorFlagDisabled();
+        mShelfRefactor.assertDisabled();
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
         hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -140,7 +143,7 @@
 
     public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
             NotificationRoundnessManager roundnessManager) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mAmbientState = ambientState;
         mHostLayout = hostLayout;
         mRoundnessManager = roundnessManager;
@@ -268,7 +271,7 @@
         }
 
         final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
-        if (mSensitiveRevealAnimEnabled && viewState.hidden) {
+        if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) {
             // if the shelf is hidden, position it at the end of the stack (plus the clip
             // padding), such that when it appears animated, it will smoothly move in from the
             // bottom, without jump cutting any notifications
@@ -279,7 +282,7 @@
     }
 
     private int getSpeedBumpIndex() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getSpeedBumpIndex();
         } else {
             return mHostLayoutController.getSpeedBumpIndex();
@@ -413,7 +416,7 @@
                     expandingAnimated, isLastChild, shelfClipStart);
 
             // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
-            if ((!mSensitiveRevealAnimEnabled && ((isLastChild && !child.isInShelf())
+            if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf())
                     || backgroundForceHidden)) || aboveShelf) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
@@ -462,7 +465,7 @@
                 // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
                 // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
                 // so we use that when on the keyguard (and while animating away) to reduce curling.
-                final float keyguardSafeShelfStart = !mSensitiveRevealAnimEnabled
+                final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled()
                         && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
                 updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
             }
@@ -504,7 +507,7 @@
     }
 
     private ExpandableView getHostLayoutChildAt(int index) {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return (ExpandableView) mHostLayout.getChildAt(index);
         } else {
             return mHostLayoutController.getChildAt(index);
@@ -512,7 +515,7 @@
     }
 
     private int getHostLayoutChildCount() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getChildCount();
         } else {
             return mHostLayoutController.getChildCount();
@@ -520,7 +523,7 @@
     }
 
     private boolean canModifyColorOfNotifications() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
         } else {
             return mController.canModifyColorOfNotifications();
@@ -583,7 +586,7 @@
     }
 
     private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
-        if (!mShelfRefactorFlagEnabled) {
+        if (!mShelfRefactor.isEnabled()) {
             return mHostLayoutController.isViewAffectedBySwipe(expandableView);
         } else {
             return mRoundnessManager.isViewAffectedBySwipe(expandableView);
@@ -607,7 +610,7 @@
     }
 
     private View getHostLayoutTransientView(int index) {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getTransientView(index);
         } else {
             return mHostLayoutController.getTransientView(index);
@@ -615,7 +618,7 @@
     }
 
     private int getHostLayoutTransientViewCount() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getTransientViewCount();
         } else {
             return mHostLayoutController.getTransientViewCount();
@@ -961,7 +964,7 @@
 
     @Override
     public void onStateChanged(int newState) {
-        assertRefactorFlagDisabled();
+        mShelfRefactor.assertDisabled();
         mStatusBarState = newState;
         updateInteractiveness();
     }
@@ -975,7 +978,7 @@
     }
 
     private boolean canInteract() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mCanInteract;
         } else {
             return mStatusBarState == StatusBarState.KEYGUARD;
@@ -1018,32 +1021,18 @@
         return false;
     }
 
-    private void assertRefactorFlagDisabled() {
-        if (mShelfRefactorFlagEnabled) {
-            NotificationShelfController.throwIllegalFlagStateError(false);
-        }
-    }
-
-    private boolean checkRefactorFlagEnabled() {
-        if (!mShelfRefactorFlagEnabled) {
-            Log.wtf(TAG,
-                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
-        }
-        return mShelfRefactorFlagEnabled;
-    }
-
     public void setController(NotificationShelfController notificationShelfController) {
-        assertRefactorFlagDisabled();
+        mShelfRefactor.assertDisabled();
         mController = notificationShelfController;
     }
 
     public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mCanModifyColorOfNotifications = canModifyColorOfNotifications;
     }
 
     public void setCanInteract(boolean canInteract) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mCanInteract = canInteract;
         updateInteractiveness();
     }
@@ -1053,27 +1042,15 @@
     }
 
     private int getIndexOfViewInHostLayout(ExpandableView child) {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.indexOfChild(child);
         } else {
             return mHostLayoutController.indexOfChild(child);
         }
     }
 
-    /**
-     * Set whether the sensitive reveal animation feature flag is enabled
-     * @param enabled true if enabled
-     */
-    public void setSensitiveRevealAnimEnabled(boolean enabled) {
-        mSensitiveRevealAnimEnabled = enabled;
-    }
-
-    public void setRefactorFlagEnabled(boolean enabled) {
-        mShelfRefactorFlagEnabled = enabled;
-    }
-
     public void requestRoundnessResetFor(ExpandableView child) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         child.requestRoundnessReset(SHELF_SCROLL);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
index 1619dda..8a3e217 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -16,11 +16,8 @@
 
 package com.android.systemui.statusbar
 
-import android.util.Log
 import android.view.View
 import android.view.View.OnClickListener
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -49,29 +46,4 @@
 
     /** @see View.setOnClickListener */
     fun setOnClickListener(listener: OnClickListener)
-
-    companion object {
-        @JvmStatic
-        fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
-            if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
-                throwIllegalFlagStateError(expected = false)
-            }
-        }
-
-        @JvmStatic
-        fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
-            featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
-                if (!enabled) {
-                    Log.wtf("NotifShelf", getErrorMessage(expected = true))
-                }
-            }
-
-        @JvmStatic
-        fun throwIllegalFlagStateError(expected: Boolean): Nothing =
-            error(getErrorMessage(expected))
-
-        private fun getErrorMessage(expected: Boolean): String =
-            "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
-                if (expected) "disabled" else "enabled"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
deleted file mode 100644
index d6f6c2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.plugins.DarkIconDispatcher.isInAreas;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-
-import java.util.ArrayList;
-
-/**
- * View group for the mobile icon in the status bar
- */
-public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
-        StatusIconDisplayable {
-    private static final String TAG = "StatusBarMobileView";
-
-    /// Used to show etc dots
-    private StatusBarIconView mDotView;
-    /// The main icon view
-    private LinearLayout mMobileGroup;
-    private String mSlot;
-    private MobileIconState mState;
-    private SignalDrawable mMobileDrawable;
-    private View mInoutContainer;
-    private ImageView mIn;
-    private ImageView mOut;
-    private ImageView mMobile, mMobileType, mMobileRoaming;
-    private View mMobileRoamingSpace;
-    @StatusBarIconView.VisibleState
-    private int mVisibleState = STATE_HIDDEN;
-    private DualToneHandler mDualToneHandler;
-    private boolean mForceHidden;
-
-    /**
-     * Designated constructor
-     *
-     * This view is special, in that it is the only view in SystemUI that allows for a configuration
-     * override on a MCC/MNC-basis. This means that for every mobile view inflated, we have to
-     * construct a context with that override, since the resource system doesn't have a way to
-     * handle this for us.
-     *
-     * @param context A context with resources configured by MCC/MNC
-     * @param slot The string key defining which slot this icon refers to. Always "mobile" for the
-     *             mobile icon
-     */
-    public static StatusBarMobileView fromContext(
-            Context context,
-            String slot
-    ) {
-        LayoutInflater inflater = LayoutInflater.from(context);
-        StatusBarMobileView v = (StatusBarMobileView)
-                inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
-        v.setSlot(slot);
-        v.init();
-        v.setVisibleState(STATE_ICON);
-        return v;
-    }
-
-    public StatusBarMobileView(Context context) {
-        super(context);
-    }
-
-    public StatusBarMobileView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public void getDrawingRect(Rect outRect) {
-        super.getDrawingRect(outRect);
-        float translationX = getTranslationX();
-        float translationY = getTranslationY();
-        outRect.left += translationX;
-        outRect.right += translationX;
-        outRect.top += translationY;
-        outRect.bottom += translationY;
-    }
-
-    private void init() {
-        mDualToneHandler = new DualToneHandler(getContext());
-        mMobileGroup = findViewById(R.id.mobile_group);
-        mMobile = findViewById(R.id.mobile_signal);
-        mMobileType = findViewById(R.id.mobile_type);
-        mMobileRoaming = findViewById(R.id.mobile_roaming);
-        mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space);
-        mIn = findViewById(R.id.mobile_in);
-        mOut = findViewById(R.id.mobile_out);
-        mInoutContainer = findViewById(R.id.inout_container);
-
-        mMobileDrawable = new SignalDrawable(getContext());
-        mMobile.setImageDrawable(mMobileDrawable);
-
-        initDotView();
-    }
-
-    private void initDotView() {
-        mDotView = new StatusBarIconView(mContext, mSlot, null);
-        mDotView.setVisibleState(STATE_DOT);
-
-        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size_sp);
-        LayoutParams lp = new LayoutParams(width, width);
-        lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
-        addView(mDotView, lp);
-    }
-
-    public void applyMobileState(MobileIconState state) {
-        boolean requestLayout = false;
-        if (state == null) {
-            requestLayout = getVisibility() != View.GONE;
-            setVisibility(View.GONE);
-            mState = null;
-        } else if (mState == null) {
-            requestLayout = true;
-            mState = state.copy();
-            initViewState();
-        } else if (!mState.equals(state)) {
-            requestLayout = updateState(state.copy());
-        }
-
-        if (requestLayout) {
-            requestLayout();
-        }
-    }
-
-    private void initViewState() {
-        setContentDescription(mState.contentDescription);
-        if (!mState.visible || mForceHidden) {
-            mMobileGroup.setVisibility(View.GONE);
-        } else {
-            mMobileGroup.setVisibility(View.VISIBLE);
-        }
-        mMobileDrawable.setLevel(mState.strengthId);
-        if (mState.typeId > 0) {
-            mMobileType.setContentDescription(mState.typeContentDescription);
-            mMobileType.setImageResource(mState.typeId);
-            mMobileType.setVisibility(View.VISIBLE);
-        } else {
-            mMobileType.setVisibility(View.GONE);
-        }
-        mMobile.setVisibility(mState.showTriangle ? View.VISIBLE : View.GONE);
-        mMobileRoaming.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
-        mMobileRoamingSpace.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
-        mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility((mState.activityIn || mState.activityOut)
-                ? View.VISIBLE : View.GONE);
-    }
-
-    private boolean updateState(MobileIconState state) {
-        boolean needsLayout = false;
-
-        setContentDescription(state.contentDescription);
-        int newVisibility = state.visible && !mForceHidden ? View.VISIBLE : View.GONE;
-        if (newVisibility != mMobileGroup.getVisibility() && STATE_ICON == mVisibleState) {
-            mMobileGroup.setVisibility(newVisibility);
-            needsLayout = true;
-        }
-        if (mState.strengthId != state.strengthId) {
-            mMobileDrawable.setLevel(state.strengthId);
-        }
-        if (mState.typeId != state.typeId) {
-            needsLayout |= state.typeId == 0 || mState.typeId == 0;
-            if (state.typeId != 0) {
-                mMobileType.setContentDescription(state.typeContentDescription);
-                mMobileType.setImageResource(state.typeId);
-                mMobileType.setVisibility(View.VISIBLE);
-            } else {
-                mMobileType.setVisibility(View.GONE);
-            }
-        }
-
-        mMobile.setVisibility(state.showTriangle ? View.VISIBLE : View.GONE);
-        mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-        mMobileRoamingSpace.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-        mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility((state.activityIn || state.activityOut)
-                ? View.VISIBLE : View.GONE);
-
-        needsLayout |= state.roaming != mState.roaming
-                || state.activityIn != mState.activityIn
-                || state.activityOut != mState.activityOut
-                || state.showTriangle != mState.showTriangle;
-
-        mState = state;
-        return needsLayout;
-    }
-
-    @Override
-    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
-        float intensity = isInAreas(areas, this) ? darkIntensity : 0;
-        mMobileDrawable.setTintList(
-                ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity)));
-        ColorStateList color = ColorStateList.valueOf(getTint(areas, this, tint));
-        mIn.setImageTintList(color);
-        mOut.setImageTintList(color);
-        mMobileType.setImageTintList(color);
-        mMobileRoaming.setImageTintList(color);
-        mDotView.setDecorColor(tint);
-        mDotView.setIconColor(tint, false);
-    }
-
-    @Override
-    public String getSlot() {
-        return mSlot;
-    }
-
-    public void setSlot(String slot) {
-        mSlot = slot;
-    }
-
-    @Override
-    public void setStaticDrawableColor(int color) {
-        ColorStateList list = ColorStateList.valueOf(color);
-        mMobileDrawable.setTintList(list);
-        mIn.setImageTintList(list);
-        mOut.setImageTintList(list);
-        mMobileType.setImageTintList(list);
-        mMobileRoaming.setImageTintList(list);
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public void setDecorColor(int color) {
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public boolean isIconVisible() {
-        return mState.visible && !mForceHidden;
-    }
-
-    @Override
-    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
-        if (state == mVisibleState) {
-            return;
-        }
-
-        mVisibleState = state;
-        switch (state) {
-            case STATE_ICON:
-                mMobileGroup.setVisibility(View.VISIBLE);
-                mDotView.setVisibility(View.GONE);
-                break;
-            case STATE_DOT:
-                mMobileGroup.setVisibility(View.INVISIBLE);
-                mDotView.setVisibility(View.VISIBLE);
-                break;
-            case STATE_HIDDEN:
-            default:
-                mMobileGroup.setVisibility(View.INVISIBLE);
-                mDotView.setVisibility(View.INVISIBLE);
-                break;
-        }
-    }
-
-    /**
-     * Forces the state to be hidden (views will be GONE) and if necessary updates the layout.
-     *
-     * Makes sure that the {@link StatusBarIconController} cannot make it visible while this flag
-     * is enabled.
-     * @param forceHidden {@code true} if the icon should be GONE in its view regardless of its
-     *                                state.
-     *               {@code false} if the icon should show as determined by its controller.
-     */
-    public void forceHidden(boolean forceHidden) {
-        if (mForceHidden != forceHidden) {
-            mForceHidden = forceHidden;
-            updateState(mState);
-            requestLayout();
-        }
-    }
-
-    @Override
-    @StatusBarIconView.VisibleState
-    public int getVisibleState() {
-        return mVisibleState;
-    }
-
-    @VisibleForTesting
-    public MobileIconState getState() {
-        return mState;
-    }
-
-    @Override
-    public String toString() {
-        return "StatusBarMobileView(slot=" + mSlot + " state=" + mState + ")";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 9aa28c3..93b9ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -1282,7 +1282,7 @@
             }
         }
         String sims = args.getString("sims");
-        if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (sims != null) {
             int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
             List<SubscriptionInfo> subs = new ArrayList<>();
             if (num != mMobileSignalControllers.size()) {
@@ -1305,7 +1305,7 @@
             mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
         String mobile = args.getString("mobile");
-        if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (mobile != null) {
             boolean show = mobile.equals("show");
             String datatype = args.getString("datatype");
             String slotString = args.getString("slot");
@@ -1390,7 +1390,7 @@
             controller.notifyListeners();
         }
         String carrierNetworkChange = args.getString("carriernetworkchange");
-        if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (carrierNetworkChange != null) {
             boolean show = carrierNetworkChange.equals("show");
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index e5ba3ce..1c7a186 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -19,6 +19,7 @@
 import android.app.IActivityManager;
 import android.app.WallpaperManager;
 import android.content.Context;
+import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
 import android.service.dreams.IDreamManager;
 import android.util.Log;
@@ -146,7 +147,8 @@
             SysuiColorExtractor colorExtractor,
             KeyguardStateController keyguardStateController,
             DumpManager dumpManager,
-            WallpaperManager wallpaperManager) {
+            WallpaperManager wallpaperManager,
+            DisplayManager displayManager) {
         return new NotificationMediaManager(
                 context,
                 centralSurfacesOptionalLazy,
@@ -162,7 +164,8 @@
                 colorExtractor,
                 keyguardStateController,
                 dumpManager,
-                wallpaperManager);
+                wallpaperManager,
+                displayManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 577ad20..bac8982 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import javax.inject.Inject
@@ -25,12 +23,8 @@
 class NotifPipelineFlags
 @Inject
 constructor(
-    private val featureFlags: FeatureFlags,
-    private val sysPropFlags: FlagResolver,
+    private val featureFlags: FeatureFlags
 ) {
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
-
-    fun allowDismissOngoing(): Boolean =
-        sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 1cf9c1e..1c5aa3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,10 +3,10 @@
 import android.util.FloatProperty
 import android.view.View
 import androidx.annotation.FloatRange
-import com.android.systemui.Dependency
 import com.android.systemui.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.ViewRefactorFlag
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import kotlin.math.abs
@@ -46,14 +46,14 @@
     @JvmDefault
     val topCornerRadius: Float
         get() =
-            if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.topCornerRadius
+            if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius
             else topRoundness * maxRadius
 
     /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
     @JvmDefault
     val bottomCornerRadius: Float
         get() =
-            if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.bottomCornerRadius
+            if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius
             else bottomRoundness * maxRadius
 
     /** Get and update the current radii */
@@ -335,13 +335,12 @@
     internal val targetView: View,
     private val roundable: Roundable,
     maxRadius: Float,
-    private val featureFlags: FeatureFlags = Dependency.get(FeatureFlags::class.java)
+    featureFlags: FeatureFlags? = null
 ) {
     internal var maxRadius = maxRadius
         private set
 
-    internal val newHeadsUpAnimFlagEnabled
-        get() = featureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS)
+    internal val newHeadsUpAnim = ViewRefactorFlag(featureFlags, Flags.IMPROVED_HUN_ANIMATIONS)
 
     /** Animatable for top roundness */
     private val topAnimatable = topAnimatable(roundable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 7898736..affd2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -791,28 +791,6 @@
         return !mSbn.isOngoing() || !isLocked;
     }
 
-    /**
-     * @return Can the underlying notification be individually dismissed?
-     * @see #canViewBeDismissed()
-     */
-    // TODO: This logic doesn't belong on NotificationEntry. It should be moved to a controller
-    // that can be added as a dependency to any class that needs to answer this question.
-    public boolean legacyIsDismissableRecursive() {
-        if  (mSbn.isOngoing()) {
-            return false;
-        }
-        List<NotificationEntry> children = getAttachedNotifChildren();
-        if (children != null && children.size() > 0) {
-            for (int i = 0; i < children.size(); i++) {
-                NotificationEntry child =  children.get(i);
-                if (child.getSbn().isOngoing()) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
     public boolean canViewBeDismissed() {
         if (row == null) return true;
         return row.canViewBeDismissed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 1896080..9ecf50e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
-import static com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -176,8 +175,6 @@
         entry.setRow(row);
         mNotifBindPipeline.manageRow(entry, row);
         mPresenter.onBindRow(row);
-        row.setInlineReplyAnimationFlagEnabled(
-                mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
index b318252..78e9a74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.withIncreasedIndent
@@ -28,9 +27,7 @@
 import javax.inject.Inject
 
 @SysUISingleton
-class NotificationDismissibilityProviderImpl
-@Inject
-constructor(private val notifPipelineFlags: NotifPipelineFlags, dumpManager: DumpManager) :
+class NotificationDismissibilityProviderImpl @Inject constructor(dumpManager: DumpManager) :
     NotificationDismissibilityProvider, Dumpable {
 
     init {
@@ -43,11 +40,7 @@
         private set
 
     override fun isDismissable(entry: NotificationEntry): Boolean {
-        return if (notifPipelineFlags.allowDismissOngoing()) {
-            entry.key !in nonDismissableEntryKeys
-        } else {
-            entry.legacyIsDismissableRecursive()
-        }
+        return entry.key !in nonDismissableEntryKeys
     }
 
     @Synchronized
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 908c11a..36a8e98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -566,7 +566,7 @@
 
     @Override
     public float getTopCornerRadius() {
-        if (isNewHeadsUpAnimFlagEnabled()) {
+        if (mImprovedHunAnimation.isEnabled()) {
             return super.getTopCornerRadius();
         }
 
@@ -576,7 +576,7 @@
 
     @Override
     public float getBottomCornerRadius() {
-        if (isNewHeadsUpAnimFlagEnabled()) {
+        if (mImprovedHunAnimation.isEnabled()) {
             return super.getBottomCornerRadius();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index b34c281..42b99a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -77,6 +77,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -275,7 +276,8 @@
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
-    private boolean mIsInlineReplyAnimationFlagEnabled = false;
+    private final ViewRefactorFlag mInlineReplyAnimation =
+            new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -3121,10 +3123,6 @@
         return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
     }
 
-    public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
-        mIsInlineReplyAnimationFlagEnabled = isEnabled;
-    }
-
     @Override
     public void setActualHeight(int height, boolean notifyListeners) {
         boolean changed = height != getActualHeight();
@@ -3144,7 +3142,7 @@
         }
         int contentHeight = Math.max(getMinHeight(), height);
         for (NotificationContentView l : mLayouts) {
-            if (mIsInlineReplyAnimationFlagEnabled) {
+            if (mInlineReplyAnimation.isEnabled()) {
                 l.setContentHeight(height);
             } else {
                 l.setContentHeight(contentHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 7f23c1b..c8f13a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,10 +28,9 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.util.DumpUtilsKt;
@@ -50,7 +49,8 @@
     private float mOutlineAlpha = -1f;
     private boolean mAlwaysRoundBothCorners;
     private Path mTmpPath = new Path();
-    private final FeatureFlags mFeatureFlags;
+    protected final ViewRefactorFlag mImprovedHunAnimation =
+            new ViewRefactorFlag(Flags.IMPROVED_HUN_ANIMATIONS);
 
     /**
      * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -126,7 +126,7 @@
             return EMPTY_PATH;
         }
         float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
-        if (!isNewHeadsUpAnimFlagEnabled() && (topRadius + bottomRadius > height)) {
+        if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
             float overShoot = topRadius + bottomRadius - height;
             float currentTopRoundness = getTopRoundness();
             float currentBottomRoundness = getBottomRoundness();
@@ -167,7 +167,6 @@
         super(context, attrs);
         setOutlineProvider(mProvider);
         initDimens();
-        mFeatureFlags = Dependency.get(FeatureFlags.class);
     }
 
     @Override
@@ -376,8 +375,4 @@
         });
     }
 
-    // TODO(b/290365128) replace with ViewRefactorFlag
-    protected boolean isNewHeadsUpAnimFlagEnabled() {
-        return mFeatureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 23a58d2..22a87a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -21,7 +21,6 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
@@ -66,7 +65,7 @@
     override fun setOnClickListener(listener: View.OnClickListener) = unsupported
 
     private val unsupported: Nothing
-        get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
+        get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled")
 }
 
 /** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
@@ -80,8 +79,6 @@
     ) {
         ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
         shelf.apply {
-            setRefactorFlagEnabled(true)
-            setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
             // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
             notificationIconAreaController.setShelfIcons(shelfIcons)
             repeatWhenAttached {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index b0f3f59..95e74f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -29,7 +29,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
@@ -57,7 +56,6 @@
     private final SectionProvider mSectionProvider;
     private final BypassController mBypassController;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    private final FeatureFlags mFeatureFlags;
     /**
      *  Used to read bouncer states.
      */
@@ -261,13 +259,12 @@
             @NonNull SectionProvider sectionProvider,
             @NonNull BypassController bypassController,
             @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            @NonNull FeatureFlags featureFlags) {
+            @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator
+    ) {
         mSectionProvider = sectionProvider;
         mBypassController = bypassController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
-        mFeatureFlags = featureFlags;
         reload(context);
         dumpManager.registerDumpable(this);
     }
@@ -753,10 +750,6 @@
         return mLargeScreenShadeInterpolator;
     }
 
-    public FeatureFlags getFeatureFlags() {
-        return mFeatureFlags;
-    }
-
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("mTopPadding=" + mTopPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c1ceb3c..d71bc2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -89,6 +89,7 @@
 import com.android.systemui.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.shade.ShadeController;
@@ -198,7 +199,8 @@
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
     private final boolean mSensitiveRevealAnimEndabled;
-    private boolean mAnimatedInsets;
+    private final ViewRefactorFlag mAnimatedInsets;
+    private final ViewRefactorFlag mShelfRefactor;
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
@@ -621,7 +623,9 @@
         mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
         mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-        setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
+        mAnimatedInsets =
+                new ViewRefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
+        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -660,7 +664,7 @@
         mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
         mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-        if (mAnimatedInsets) {
+        if (mAnimatedInsets.isEnabled()) {
             setWindowInsetsAnimationCallback(mInsetsCallback);
         }
     }
@@ -730,11 +734,6 @@
     }
 
     @VisibleForTesting
-    void setAnimatedInsetsEnabled(boolean enabled) {
-        mAnimatedInsets = enabled;
-    }
-
-    @VisibleForTesting
     public void updateFooter() {
         if (mFooterView == null) {
             return;
@@ -1773,7 +1772,7 @@
             return;
         }
         mForcedScroll = v;
-        if (mAnimatedInsets) {
+        if (mAnimatedInsets.isEnabled()) {
             updateForcedScroll();
         } else {
             scrollTo(v);
@@ -1822,7 +1821,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (!mAnimatedInsets) {
+        if (!mAnimatedInsets.isEnabled()) {
             mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
         }
         mWaterfallTopInset = 0;
@@ -1830,11 +1829,11 @@
         if (cutout != null) {
             mWaterfallTopInset = cutout.getWaterfallInsets().top;
         }
-        if (mAnimatedInsets && !mIsInsetAnimationRunning) {
+        if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) {
             // update bottom inset e.g. after rotation
             updateBottomInset(insets);
         }
-        if (!mAnimatedInsets) {
+        if (!mAnimatedInsets.isEnabled()) {
             int range = getScrollRange();
             if (mOwnScrollY > range) {
                 // HACK: We're repeatedly getting staggered insets here while the IME is
@@ -2714,7 +2713,7 @@
      * @param listener callback for notification removed
      */
     public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
-        NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
+        mShelfRefactor.assertDisabled();
         mOnNotificationRemovedListener = listener;
     }
 
@@ -2727,7 +2726,7 @@
         if (!mChildTransferInProgress) {
             onViewRemovedInternal(expandableView, this);
         }
-        if (mAmbientState.getFeatureFlags().isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+        if (mShelfRefactor.isEnabled()) {
             mShelf.requestRoundnessResetFor(expandableView);
         } else {
             if (mOnNotificationRemovedListener != null) {
@@ -4943,18 +4942,12 @@
 
     @Nullable
     public ExpandableView getShelf() {
-        if (NotificationShelfController.checkRefactorFlagEnabled(mAmbientState.getFeatureFlags())) {
-            return mShelf;
-        } else {
-            return null;
-        }
+        if (!mShelfRefactor.expectEnabled()) return null;
+        return mShelf;
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!NotificationShelfController.checkRefactorFlagEnabled(
-                mAmbientState.getFeatureFlags())) {
-            return;
-        }
+        if (!mShelfRefactor.expectEnabled()) return;
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
@@ -4968,7 +4961,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
+        mShelfRefactor.assertDisabled();
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index ef7375a..4668aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -65,6 +65,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -205,6 +206,7 @@
     private boolean mIsInTransitionToAod = false;
 
     private final FeatureFlags mFeatureFlags;
+    private final ViewRefactorFlag mShelfRefactor;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
@@ -718,6 +720,7 @@
         mShadeController = shadeController;
         mNotifIconAreaController = notifIconAreaController;
         mFeatureFlags = featureFlags;
+        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mNotificationTargetsHelper = notificationTargetsHelper;
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
@@ -1432,7 +1435,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
+        mShelfRefactor.assertDisabled();
         mView.setShelfController(notificationShelfController);
     }
 
@@ -1645,12 +1648,12 @@
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mView.setShelf(shelf);
     }
 
     public int getShelfHeight() {
-        if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) {
+        if (!mShelfRefactor.expectEnabled()) {
             return 0;
         }
         ExpandableView shelf = mView.getShelf();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 8361d6b..c3c9a61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2061,6 +2061,7 @@
     void updateDisplaySize() {
         mDisplay.getMetrics(mDisplayMetrics);
         mDisplay.getSize(mCurrentDisplaySize);
+        mMediaManager.onDisplayUpdated(mDisplay);
         if (DEBUG_GESTURES) {
             mGestureRec.tag("display",
                     String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 8e9f382..374543d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
@@ -34,10 +33,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
-import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
@@ -52,7 +48,6 @@
     private static final String TAG = "DemoStatusIcons";
 
     private final LinearLayout mStatusIcons;
-    private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
     private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
     private final int mIconSize;
 
@@ -91,7 +86,6 @@
     }
 
     public void remove() {
-        mMobileViews.clear();
         ((ViewGroup) getParent()).removeView(this);
     }
 
@@ -127,7 +121,6 @@
         mDemoMode = false;
         mStatusIcons.setVisibility(View.VISIBLE);
         mModernMobileViews.clear();
-        mMobileViews.clear();
         setVisibility(View.GONE);
     }
 
@@ -236,22 +229,6 @@
     }
 
     /**
-     * Add a new mobile icon view
-     */
-    public void addMobileView(MobileIconState state, Context mobileContext) {
-        Log.d(TAG, "addMobileView: ");
-        StatusBarMobileView view = StatusBarMobileView
-                .fromContext(mobileContext, state.slot);
-
-        view.applyMobileState(state);
-        view.setStaticDrawableColor(mColor);
-
-        // mobile always goes at the end
-        mMobileViews.add(view);
-        addView(view, getChildCount(), createLayoutParams());
-    }
-
-    /**
      * Add a {@link ModernStatusBarMobileView}
      * @param mobileContext possibly mcc/mnc overridden mobile context
      * @param subId the subscriptionId for this mobile view
@@ -285,8 +262,7 @@
         // If we have mobile views, put wifi before them
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            if (child instanceof StatusBarMobileView
-                    || child instanceof ModernStatusBarMobileView) {
+            if (child instanceof ModernStatusBarMobileView) {
                 viewIndex = i;
                 break;
             }
@@ -297,26 +273,6 @@
         addView(view, viewIndex, createLayoutParams());
     }
 
-    /**
-     * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
-     * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
-     * update it, since the context (and thus the {@link Configuration}) may have changed
-     */
-    public void updateMobileState(MobileIconState state, Context mobileContext) {
-        Log.d(TAG, "updateMobileState: " + state);
-
-        // The mobile config provided by MobileContextProvider could have changed; always recreate
-        for (int i = 0; i < mMobileViews.size(); i++) {
-            StatusBarMobileView view = mMobileViews.get(i);
-            if (view.getState().subId == state.subId) {
-                removeView(view);
-            }
-        }
-
-        // Add the replacement or new icon
-        addMobileView(state, mobileContext);
-    }
-
     public void onRemoveIcon(StatusIconDisplayable view) {
         if (view.getSlot().equals("wifi")) {
             if (view instanceof ModernStatusBarWifiView) {
@@ -324,12 +280,6 @@
                 removeView(mModernWifiView);
                 mModernWifiView = null;
             }
-        } else if (view instanceof StatusBarMobileView) {
-            StatusBarMobileView mobileView = matchingMobileView(view);
-            if (mobileView != null) {
-                removeView(mobileView);
-                mMobileViews.remove(mobileView);
-            }
         } else if (view instanceof ModernStatusBarMobileView) {
             ModernStatusBarMobileView mobileView = matchingModernMobileView(
                     (ModernStatusBarMobileView) view);
@@ -340,21 +290,6 @@
         }
     }
 
-    private StatusBarMobileView matchingMobileView(StatusIconDisplayable otherView) {
-        if (!(otherView instanceof StatusBarMobileView)) {
-            return null;
-        }
-
-        StatusBarMobileView v = (StatusBarMobileView) otherView;
-        for (StatusBarMobileView view : mMobileViews) {
-            if (view.getState().subId == v.getState().subId) {
-                return view;
-            }
-        }
-
-        return null;
-    }
-
     private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) {
         for (ModernStatusBarMobileView v : mModernMobileViews) {
             if (v.getSubId() == other.getSubId()) {
@@ -376,9 +311,6 @@
         if (mModernWifiView != null) {
             mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
         }
-        for (StatusBarMobileView view : mMobileViews) {
-            view.onDarkChanged(areas, darkIntensity, tint);
-        }
         for (ModernStatusBarMobileView view : mModernMobileViews) {
             view.onDarkChanged(areas, darkIntensity, tint);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index b2c39f7..92c786f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -306,19 +306,25 @@
 
     /**
      * Drawable that aligns left horizontally and center vertically (like ImageWallpaper).
+     *
+     * <p>Aligns to the center when showing on the smaller internal display of a multi display
+     * device.
      */
     public static class WallpaperDrawable extends DrawableWrapper {
 
         private final ConstantState mState;
         private final Rect mTmpRect = new Rect();
+        private boolean mIsOnSmallerInternalDisplays;
 
-        public WallpaperDrawable(Resources r, Bitmap b) {
-            this(r, new ConstantState(b));
+        public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) {
+            this(r, new ConstantState(b), isOnSmallerInternalDisplays);
         }
 
-        private WallpaperDrawable(Resources r, ConstantState state) {
+        private WallpaperDrawable(Resources r, ConstantState state,
+                boolean isOnSmallerInternalDisplays) {
             super(new BitmapDrawable(r, state.mBackground));
             mState = state;
+            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
         }
 
         @Override
@@ -357,10 +363,17 @@
             }
             dy = (vheight - dheight * scale) * 0.5f;
 
+            int offsetX = 0;
+            // Offset to show the center area of the wallpaper on a smaller display for multi
+            // display device
+            if (mIsOnSmallerInternalDisplays) {
+                offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2);
+            }
+
             mTmpRect.set(
-                    bounds.left,
+                    bounds.left + offsetX,
                     bounds.top + Math.round(dy),
-                    bounds.left + Math.round(dwidth * scale),
+                    bounds.left + Math.round(dwidth * scale) + offsetX,
                     bounds.top + Math.round(dheight * scale + dy));
 
             super.onBoundsChange(mTmpRect);
@@ -371,6 +384,17 @@
             return mState;
         }
 
+        /**
+         * Update bounds when the hosting display or the display size has changed.
+         *
+         * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal
+         *                                    displays with the smaller area.
+         */
+        public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) {
+            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
+            onBoundsChange(getBounds());
+        }
+
         static class ConstantState extends Drawable.ConstantState {
 
             private final Bitmap mBackground;
@@ -386,7 +410,7 @@
 
             @Override
             public Drawable newDrawable(@Nullable Resources res) {
-                return new WallpaperDrawable(res, this);
+                return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false);
             }
 
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index e18c9d8..0bf0f4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -24,6 +24,8 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -88,7 +90,7 @@
     private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final Context mContext;
 
-    private final FeatureFlags mFeatureFlags;
+    private final ViewRefactorFlag mShelfRefactor;
 
     private int mAodIconAppearTranslation;
 
@@ -120,12 +122,13 @@
             Optional<Bubbles> bubblesOptional,
             DemoModeController demoModeController,
             DarkIconDispatcher darkIconDispatcher,
-            FeatureFlags featureFlags, StatusBarWindowController statusBarWindowController,
+            FeatureFlags featureFlags,
+            StatusBarWindowController statusBarWindowController,
             ScreenOffAnimationController screenOffAnimationController) {
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
-        mFeatureFlags = featureFlags;
+        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mStatusBarStateController.addCallback(this);
         mMediaManager = notificationMediaManager;
         mDozeParameters = dozeParameters;
@@ -179,12 +182,12 @@
     }
 
     public void setupShelf(NotificationShelfController notificationShelfController) {
-        NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
+        mShelfRefactor.assertDisabled();
         mShelfIcons = notificationShelfController.getShelfIcons();
     }
 
     public void setShelfIcons(NotificationIconContainer icons) {
-        if (NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) {
+        if (mShelfRefactor.expectEnabled()) {
             mShelfIcons = icons;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 42b0a4f..d5cb6b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -15,7 +15,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
 
@@ -24,10 +23,8 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.ArraySet;
-import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
 
@@ -41,12 +38,9 @@
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
@@ -100,13 +94,10 @@
      */
     void setNewWifiIcon();
 
-    /** */
-    void setMobileIcons(String slot, List<MobileIconState> states);
-
     /**
-     * This method completely replaces {@link #setMobileIcons} with the information from the new
-     * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
-     * to worry about funneling MobileIconState objects through anymore.
+     * Notify this class that there is a new set of mobile icons to display, keyed off of this list
+     * of subIds. The icons will be added and bound to the mobile data pipeline via
+     * {@link com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder}.
      */
     void setNewMobileIconSubIds(List<Integer> subIds);
     /**
@@ -168,14 +159,12 @@
         public DarkIconManager(
                 LinearLayout linearLayout,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
@@ -235,7 +224,6 @@
 
         @SysUISingleton
         public static class Factory {
-            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
@@ -243,12 +231,10 @@
 
             @Inject
             public Factory(
-                    StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiUiAdapter wifiUiAdapter,
                     MobileContextProvider mobileContextProvider,
                     MobileUiAdapter mobileUiAdapter,
                     DarkIconDispatcher darkIconDispatcher) {
-                mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiUiAdapter = wifiUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
                 mMobileUiAdapter = mobileUiAdapter;
@@ -259,7 +245,6 @@
                 return new DarkIconManager(
                         group,
                         location,
-                        mStatusBarPipelineFlags,
                         mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider,
@@ -277,14 +262,12 @@
         public TintedIconManager(
                 ViewGroup group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             super(group,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
@@ -319,19 +302,16 @@
 
         @SysUISingleton
         public static class Factory {
-            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
 
             @Inject
             public Factory(
-                    StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiUiAdapter wifiUiAdapter,
                     MobileUiAdapter mobileUiAdapter,
                     MobileContextProvider mobileContextProvider
             ) {
-                mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiUiAdapter = wifiUiAdapter;
                 mMobileUiAdapter = mobileUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
@@ -341,7 +321,6 @@
                 return new TintedIconManager(
                         group,
                         location,
-                        mStatusBarPipelineFlags,
                         mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider);
@@ -354,7 +333,6 @@
      */
     class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
-        private final StatusBarPipelineFlags mStatusBarPipelineFlags;
         private final MobileContextProvider mMobileContextProvider;
         private final LocationBasedWifiViewModel mWifiViewModel;
         private final MobileIconsViewModel mMobileIconsViewModel;
@@ -376,27 +354,21 @@
         public IconManager(
                 ViewGroup group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             mGroup = group;
-            mStatusBarPipelineFlags = statusBarPipelineFlags;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
             mLocation = location;
 
             reloadDimens();
 
-            if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
-                // This starts the flow for the new pipeline, and will notify us of changes if
-                // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
-                mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
-                MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
-            } else {
-                mMobileIconsViewModel = null;
-            }
+            // This starts the flow for the new pipeline, and will notify us of changes via
+            // {@link #setNewMobileIconIds}
+            mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
+            MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
 
             mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
         }
@@ -449,9 +421,6 @@
                 case TYPE_WIFI_NEW:
                     return addNewWifiIcon(index, slot);
 
-                case TYPE_MOBILE:
-                    return addMobileIcon(index, slot, holder.getMobileState());
-
                 case TYPE_MOBILE_NEW:
                     return addNewMobileIcon(index, slot, holder.getTag());
             }
@@ -479,40 +448,12 @@
             return view;
         }
 
-        @VisibleForTesting
-        protected StatusIconDisplayable addMobileIcon(
-                int index,
-                String slot,
-                MobileIconState state
-        ) {
-            if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-                throw new IllegalStateException("Attempting to add a mobile icon while the new "
-                        + "icons are enabled is not supported");
-            }
-
-            // Use the `subId` field as a key to query for the correct context
-            StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
-            mobileView.applyMobileState(state);
-            mGroup.addView(mobileView, index, onCreateLayoutParams());
-
-            if (mIsInDemoMode) {
-                Context mobileContext = mMobileContextProvider
-                        .getMobileContextForSub(state.subId, mContext);
-                mDemoStatusIcons.addMobileView(state, mobileContext);
-            }
-            return mobileView;
-        }
 
         protected StatusIconDisplayable addNewMobileIcon(
                 int index,
                 String slot,
                 int subId
         ) {
-            if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
-                throw new IllegalStateException("Attempting to add a mobile icon using the new"
-                        + "pipeline, but the enabled flag is false.");
-            }
-
             BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
             mGroup.addView(view, index, onCreateLayoutParams());
 
@@ -536,13 +477,6 @@
             return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
         }
 
-        private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
-            Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
-            StatusBarMobileView view = StatusBarMobileView
-                    .fromContext(mobileContext, slot);
-            return view;
-        }
-
         private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
                 String slot, int subId) {
             Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
@@ -568,15 +502,6 @@
                     com.android.internal.R.dimen.status_bar_icon_size_sp);
         }
 
-        private void setHeightAndCenter(ImageView imageView, int height) {
-            ViewGroup.LayoutParams params = imageView.getLayoutParams();
-            params.height = height;
-            if (params instanceof LinearLayout.LayoutParams) {
-                ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
-            }
-            imageView.setLayoutParams(params);
-        }
-
         protected void onRemoveIcon(int viewIndex) {
             if (mIsInDemoMode) {
                 mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
@@ -594,9 +519,6 @@
                 case TYPE_ICON:
                     onSetIcon(viewIndex, holder.getIcon());
                     return;
-                case TYPE_MOBILE:
-                    onSetMobileIcon(viewIndex, holder.getMobileState());
-                    return;
                 case TYPE_MOBILE_NEW:
                 case TYPE_WIFI_NEW:
                     // Nothing, the new icons update themselves
@@ -606,23 +528,6 @@
             }
         }
 
-        public void onSetMobileIcon(int viewIndex, MobileIconState state) {
-            View view = mGroup.getChildAt(viewIndex);
-            if (view instanceof StatusBarMobileView) {
-                ((StatusBarMobileView) view).applyMobileState(state);
-            } else {
-                // ModernStatusBarMobileView automatically updates via the ViewModel
-                throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
-                        + "the new pipeline");
-            }
-
-            if (mIsInDemoMode) {
-                Context mobileContext = mMobileContextProvider
-                        .getMobileContextForSub(state.subId, mContext);
-                mDemoStatusIcons.updateMobileState(state, mobileContext);
-            }
-        }
-
         @Override
         public void dispatchDemoCommand(String command, Bundle args) {
             if (!mDemoable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index d1a02d6..553cbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -215,41 +214,10 @@
     /**
      * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
      * by subId. Don't worry this definitely makes sense and works.
-     * @param slot da slot
-     * @param iconStates All of the mobile icon states
+     * @param subIds list of subscription ID integers that provide the key to the icon to display.
      */
     @Override
-    public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
-        if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
-                    + "icons are enabled");
-            return;
-        }
-        Slot mobileSlot = mStatusBarIconList.getSlot(slot);
-
-        // Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
-        // StatusBarIconList has UI design that first items go to the right of second items.
-        Collections.reverse(iconStates);
-
-        for (MobileIconState state : iconStates) {
-            StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
-            if (holder == null) {
-                holder = StatusBarIconHolder.fromMobileIconState(state);
-                setIcon(slot, holder);
-            } else {
-                holder.setMobileState(state);
-                handleSet(slot, holder);
-            }
-        }
-    }
-
-    @Override
     public void setNewMobileIconSubIds(List<Integer> subIds) {
-        if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring new pipeline callback, "
-                    + "since the new mobile icons are disabled");
-            return;
-        }
         String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
         Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 01fd247..7048a78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -24,7 +24,6 @@
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
 
 import java.lang.annotation.Retention;
@@ -35,7 +34,6 @@
  */
 public class StatusBarIconHolder {
     public static final int TYPE_ICON = 0;
-    public static final int TYPE_MOBILE = 2;
     /**
      * TODO (b/249790733): address this once the new pipeline is in place
      * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
@@ -63,7 +61,6 @@
 
     @IntDef({
             TYPE_ICON,
-            TYPE_MOBILE,
             TYPE_MOBILE_NEW,
             TYPE_WIFI_NEW
     })
@@ -71,7 +68,6 @@
     @interface IconType {}
 
     private StatusBarIcon mIcon;
-    private MobileIconState mMobileState;
     private @IconType int mType = TYPE_ICON;
     private int mTag = 0;
 
@@ -79,7 +75,6 @@
     public static String getTypeString(@IconType int type) {
         switch(type) {
             case TYPE_ICON: return "ICON";
-            case TYPE_MOBILE: return "MOBILE_OLD";
             case TYPE_MOBILE_NEW: return "MOBILE_NEW";
             case TYPE_WIFI_NEW: return "WIFI_NEW";
             default: return "UNKNOWN";
@@ -103,15 +98,6 @@
         return holder;
     }
 
-    /** */
-    public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mMobileState = state;
-        holder.mType = TYPE_MOBILE;
-        holder.mTag = state.subId;
-        return holder;
-    }
-
     /**
      * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
      * determine icon ordering and building the correct view model
@@ -153,21 +139,10 @@
         mIcon = icon;
     }
 
-    @Nullable
-    public MobileIconState getMobileState() {
-        return mMobileState;
-    }
-
-    public void setMobileState(MobileIconState state) {
-        mMobileState = state;
-    }
-
     public boolean isVisible() {
         switch (mType) {
             case TYPE_ICON:
                 return mIcon.visible;
-            case TYPE_MOBILE:
-                return mMobileState.visible;
             case TYPE_MOBILE_NEW:
             case TYPE_WIFI_NEW:
                 // The new pipeline controls visibilities via the view model and view binder, so
@@ -188,10 +163,6 @@
                 mIcon.visible = visible;
                 break;
 
-            case TYPE_MOBILE:
-                mMobileState.visible = visible;
-                break;
-
             case TYPE_MOBILE_NEW:
             case TYPE_WIFI_NEW:
                 // The new pipeline controls visibilities via the view model and view binder, so
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index a9135ac..ad8530d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -79,7 +79,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
-    private final CentralSurfaces mCentralSurfaces;
     private final NotificationsInteractor mNotificationsInteractor;
     private final NotificationStackScrollLayoutController mNsslController;
     private final LockscreenShadeTransitionController mShadeTransitionController;
@@ -107,7 +106,6 @@
             NotificationShadeWindowController notificationShadeWindowController,
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardStateController keyguardStateController,
-            CentralSurfaces centralSurfaces,
             NotificationsInteractor notificationsInteractor,
             LockscreenShadeTransitionController shadeTransitionController,
             PowerInteractor powerInteractor,
@@ -128,8 +126,6 @@
         mQsController = quickSettingsController;
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
-        // TODO: use KeyguardStateController#isOccluded to remove this dependency
-        mCentralSurfaces = centralSurfaces;
         mNotificationsInteractor = notificationsInteractor;
         mNsslController = stackScrollerController;
         mShadeTransitionController = shadeTransitionController;
@@ -283,7 +279,7 @@
         @Override
         public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
             final StatusBarNotification sbn = entry.getSbn();
-            if (mCentralSurfaces.isOccluded()) {
+            if (mKeyguardStateController.isOccluded()) {
                 boolean devicePublic = mLockscreenUserManager
                         .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
                 boolean userPublic = devicePublic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 6919996..344e56c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
-import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -27,7 +26,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.connectivity.IconState;
-import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.policy.SecurityController;
@@ -71,7 +69,6 @@
     // Track as little state as possible, and only for padding purposes
     private boolean mIsAirplaneMode = false;
 
-    private ArrayList<MobileIconState> mMobileStates = new ArrayList<>();
     private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
     private boolean mInitialized;
 
@@ -102,7 +99,7 @@
         mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
     }
 
-    /** Call to initilaize and register this classw with the system. */
+    /** Call to initialize and register this class with the system. */
     public void init() {
         if (mInitialized) {
             return;
@@ -189,34 +186,6 @@
                 CallIndicatorIconState.copyStates(mCallIndicatorStates));
     }
 
-    @Override
-    public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
-        if (DEBUG) {
-            Log.d(TAG, "setMobileDataIndicators: " + indicators);
-        }
-        MobileIconState state = getState(indicators.subId);
-        if (state == null) {
-            return;
-        }
-
-        state.visible = indicators.statusIcon.visible && !mHideMobile;
-        state.strengthId = indicators.statusIcon.icon;
-        state.typeId = indicators.statusType;
-        state.contentDescription = indicators.statusIcon.contentDescription;
-        state.typeContentDescription = indicators.typeContentDescription;
-        state.showTriangle = indicators.showTriangle;
-        state.roaming = indicators.roaming;
-        state.activityIn = indicators.activityIn && mActivityEnabled;
-        state.activityOut = indicators.activityOut && mActivityEnabled;
-
-        if (DEBUG) {
-            Log.d(TAG, "MobileIconStates: "
-                    + (mMobileStates == null ? "" : mMobileStates.toString()));
-        }
-        // Always send a copy to maintain value type semantics
-        mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));
-    }
-
     private CallIndicatorIconState getNoCallingState(int subId) {
         for (CallIndicatorIconState state : mCallIndicatorStates) {
             if (state.subId == subId) {
@@ -227,74 +196,8 @@
         return null;
     }
 
-    private MobileIconState getState(int subId) {
-        for (MobileIconState state : mMobileStates) {
-            if (state.subId == subId) {
-                return state;
-            }
-        }
-        Log.e(TAG, "Unexpected subscription " + subId);
-        return null;
-    }
-
-    /**
-     * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators
-     * so we don't have to update the icon manager at this point, just remove the old ones
-     * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8)
-     */
-    @Override
-    public void setSubs(List<SubscriptionInfo> subs) {
-        if (DEBUG) Log.d(TAG, "setSubs: " + (subs == null ? "" : subs.toString()));
-        if (hasCorrectSubs(subs)) {
-            return;
-        }
-
-        mIconController.removeAllIconsForSlot(mSlotMobile);
-        mIconController.removeAllIconsForSlot(mSlotNoCalling);
-        mIconController.removeAllIconsForSlot(mSlotCallStrength);
-        mMobileStates.clear();
-        List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
-        noCallingStates.addAll(mCallIndicatorStates);
-        mCallIndicatorStates.clear();
-        final int n = subs.size();
-        for (int i = 0; i < n; i++) {
-            mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
-            boolean isNewSub = true;
-            for (CallIndicatorIconState state : noCallingStates) {
-                if (state.subId == subs.get(i).getSubscriptionId()) {
-                    mCallIndicatorStates.add(state);
-                    isNewSub = false;
-                    break;
-                }
-            }
-            if (isNewSub) {
-                mCallIndicatorStates.add(
-                        new CallIndicatorIconState(subs.get(i).getSubscriptionId()));
-            }
-        }
-    }
-
-    private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
-        final int N = subs.size();
-        if (N != mMobileStates.size()) {
-            return false;
-        }
-        for (int i = 0; i < N; i++) {
-            if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void setNoSims(boolean show, boolean simDetected) {
-        // Noop yay!
-    }
-
     @Override
     public void setEthernetIndicators(IconState state) {
-        boolean visible = state.visible && !mHideEthernet;
         int resId = state.icon;
         String description = state.contentDescription;
 
@@ -324,11 +227,6 @@
         }
     }
 
-    @Override
-    public void setMobileDataEnabled(boolean enabled) {
-        // Don't care.
-    }
-
     /**
      * Stores the statusbar state for no Calling & SMS.
      */
@@ -388,117 +286,4 @@
             return outStates;
         }
     }
-
-    private static abstract class SignalIconState {
-        public boolean visible;
-        public boolean activityOut;
-        public boolean activityIn;
-        public String slot;
-        public String contentDescription;
-
-        @Override
-        public boolean equals(Object o) {
-            // Skipping reference equality bc this should be more of a value type
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            SignalIconState that = (SignalIconState) o;
-            return visible == that.visible &&
-                    activityOut == that.activityOut &&
-                    activityIn == that.activityIn &&
-                    Objects.equals(contentDescription, that.contentDescription) &&
-                    Objects.equals(slot, that.slot);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(visible, activityOut, slot);
-        }
-
-        protected void copyTo(SignalIconState other) {
-            other.visible = visible;
-            other.activityIn = activityIn;
-            other.activityOut = activityOut;
-            other.slot = slot;
-            other.contentDescription = contentDescription;
-        }
-    }
-
-    /**
-     * A little different. This one delegates to SignalDrawable instead of a specific resId
-     */
-    public static class MobileIconState extends SignalIconState {
-        public int subId;
-        public int strengthId;
-        public int typeId;
-        public boolean showTriangle;
-        public boolean roaming;
-        public boolean needsLeadingPadding;
-        public CharSequence typeContentDescription;
-
-        private MobileIconState(int subId) {
-            super();
-            this.subId = subId;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            if (!super.equals(o)) {
-                return false;
-            }
-            MobileIconState that = (MobileIconState) o;
-            return subId == that.subId
-                    && strengthId == that.strengthId
-                    && typeId == that.typeId
-                    && showTriangle == that.showTriangle
-                    && roaming == that.roaming
-                    && needsLeadingPadding == that.needsLeadingPadding
-                    && Objects.equals(typeContentDescription, that.typeContentDescription);
-        }
-
-        @Override
-        public int hashCode() {
-
-            return Objects
-                    .hash(super.hashCode(), subId, strengthId, typeId, showTriangle, roaming,
-                            needsLeadingPadding, typeContentDescription);
-        }
-
-        public MobileIconState copy() {
-            MobileIconState copy = new MobileIconState(this.subId);
-            copyTo(copy);
-            return copy;
-        }
-
-        public void copyTo(MobileIconState other) {
-            super.copyTo(other);
-            other.subId = subId;
-            other.strengthId = strengthId;
-            other.typeId = typeId;
-            other.showTriangle = showTriangle;
-            other.roaming = roaming;
-            other.needsLeadingPadding = needsLeadingPadding;
-            other.typeContentDescription = typeContentDescription;
-        }
-
-        private static List<MobileIconState> copyStates(List<MobileIconState> inStates) {
-            ArrayList<MobileIconState> outStates = new ArrayList<>();
-            for (MobileIconState state : inStates) {
-                MobileIconState copy = new MobileIconState(state.subId);
-                state.copyTo(copy);
-                outStates.add(copy);
-            }
-
-            return outStates;
-        }
-
-        @Override public String toString() {
-            return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId
-                    + ", showTriangle=" + showTriangle + ", roaming=" + roaming
-                    + ", typeId=" + typeId + ", visible=" + visible + ")";
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 29829e4..6e51ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -18,8 +18,6 @@
 
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
 /** All flagging methods related to the new status bar pipeline (see b/238425913). */
@@ -28,30 +26,10 @@
 @Inject
 constructor(
     context: Context,
-    private val featureFlags: FeatureFlags,
 ) {
     private val mobileSlot = context.getString(com.android.internal.R.string.status_bar_mobile)
     private val wifiSlot = context.getString(com.android.internal.R.string.status_bar_wifi)
 
-    /** True if we should display the mobile icons using the new status bar data pipeline. */
-    fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
-
-    /**
-     * True if we should run the new mobile icons backend to get the logging.
-     *
-     * Does *not* affect whether we render the mobile icons using the new backend data. See
-     * [useNewMobileIcons] for that.
-     */
-    fun runNewMobileIconsBackend(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
-
-    /**
-     * Returns true if we should apply some coloring to the icons that were rendered with the new
-     * pipeline to help with debugging.
-     */
-    fun useDebugColoring(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
-
     /**
      * For convenience in the StatusBarIconController, we want to gate some actions based on slot
      * name and the flag together.
@@ -59,5 +37,5 @@
      * @return true if this icon is controlled by any of the status bar pipeline flags
      */
     fun isIconControlledByFlags(slotName: String): Boolean =
-        slotName == wifiSlot || (slotName == mobileSlot && useNewMobileIcons())
+        slotName == wifiSlot || slotName == mobileSlot
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index a05ab84..d7fcf48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import java.io.PrintWriter
@@ -46,24 +45,18 @@
     val mobileIconsViewModel: MobileIconsViewModel,
     private val logger: MobileViewLogger,
     @Application private val scope: CoroutineScope,
-    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) : CoreStartable {
     private var isCollecting: Boolean = false
     private var lastValue: List<Int>? = null
 
     override fun start() {
-        // Only notify the icon controller if we want to *render* the new icons.
-        // Note that this flow may still run if
-        // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
-        // get the logging data without rendering.
-        if (statusBarPipelineFlags.useNewMobileIcons()) {
-            scope.launch {
-                isCollecting = true
-                mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
-                    logger.logUiAdapterSubIdsSentToIconController(it)
-                    lastValue = it
-                    iconController.setNewMobileIconSubIds(it)
-                }
+        // Start notifying the icon controller of subscriptions
+        scope.launch {
+            isCollecting = true
+            mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
+                logger.logUiAdapterSubIdsSentToIconController(it)
+                lastValue = it
+                iconController.setNewMobileIconSubIds(it)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a2a247a..c221109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -183,16 +183,10 @@
             }
 
             override fun onIconTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 iconTint.value = newTint
             }
 
             override fun onDecorTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 decorTint.value = newTint
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index f775940..a51982c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
 
 /**
@@ -32,24 +31,14 @@
  */
 abstract class LocationBasedMobileViewModel(
     val commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-    debugTint: Int,
     val locationName: String,
     val verboseLogger: VerboseMobileViewLogger?,
 ) : MobileIconViewModelCommon by commonImpl {
-    val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
-
-    val defaultColor: Int =
-        if (useDebugColoring) {
-            debugTint
-        } else {
-            Color.WHITE
-        }
+    val defaultColor: Int = Color.WHITE
 
     companion object {
         fun viewModelForLocation(
             commonImpl: MobileIconViewModelCommon,
-            statusBarPipelineFlags: StatusBarPipelineFlags,
             verboseMobileViewLogger: VerboseMobileViewLogger,
             loc: StatusBarLocation,
         ): LocationBasedMobileViewModel =
@@ -57,39 +46,31 @@
                 StatusBarLocation.HOME ->
                     HomeMobileIconViewModel(
                         commonImpl,
-                        statusBarPipelineFlags,
                         verboseMobileViewLogger,
                     )
-                StatusBarLocation.KEYGUARD ->
-                    KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
-                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+                StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl)
+                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl)
             }
     }
 }
 
 class HomeMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
     verboseMobileViewLogger: VerboseMobileViewLogger,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.CYAN,
         locationName = "Home",
         verboseMobileViewLogger,
     )
 
 class QsMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.GREEN,
         locationName = "QS",
         // Only do verbose logging for the Home location.
         verboseLogger = null,
@@ -97,13 +78,10 @@
 
 class KeyguardMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.MAGENTA,
         locationName = "Keyguard",
         // Only do verbose logging for the Home location.
         verboseLogger = null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 40b8c90..5cf887e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -98,7 +98,6 @@
         val common = commonViewModelForSub(subId)
         return LocationBasedMobileViewModel.viewModelForLocation(
             common,
-            statusBarPipelineFlags,
             verboseLogger,
             location,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index 6d71823..7a60d96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
@@ -46,7 +45,6 @@
 constructor(
     private val iconController: StatusBarIconController,
     private val wifiViewModel: WifiViewModel,
-    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) {
     /**
      * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
@@ -60,8 +58,7 @@
         statusBarIconGroup: ViewGroup,
         location: StatusBarLocation,
     ): LocationBasedWifiViewModel {
-        val locationViewModel =
-            viewModelForLocation(wifiViewModel, statusBarPipelineFlags, location)
+        val locationViewModel = viewModelForLocation(wifiViewModel, location)
 
         statusBarIconGroup.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index e819c4f..3082a66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -157,16 +157,10 @@
             }
 
             override fun onIconTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 iconTint.value = newTint
             }
 
             override fun onDecorTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 decorTint.value = newTint
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index b731a41..cd5b92c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 
 /**
  * A view model for a wifi icon in a specific location. This allows us to control parameters that
@@ -27,18 +26,9 @@
  * Must be subclassed for each distinct location.
  */
 abstract class LocationBasedWifiViewModel(
-    val commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-    debugTint: Int,
+    private val commonImpl: WifiViewModelCommon,
 ) : WifiViewModelCommon by commonImpl {
-    val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
-
-    val defaultColor: Int =
-        if (useDebugColoring) {
-            debugTint
-        } else {
-            Color.WHITE
-        }
+    val defaultColor: Int = Color.WHITE
 
     companion object {
         /**
@@ -47,13 +37,12 @@
          */
         fun viewModelForLocation(
             commonImpl: WifiViewModelCommon,
-            flags: StatusBarPipelineFlags,
             location: StatusBarLocation,
         ): LocationBasedWifiViewModel =
             when (location) {
-                StatusBarLocation.HOME -> HomeWifiViewModel(commonImpl, flags)
-                StatusBarLocation.KEYGUARD -> KeyguardWifiViewModel(commonImpl, flags)
-                StatusBarLocation.QS -> QsWifiViewModel(commonImpl, flags)
+                StatusBarLocation.HOME -> HomeWifiViewModel(commonImpl)
+                StatusBarLocation.KEYGUARD -> KeyguardWifiViewModel(commonImpl)
+                StatusBarLocation.QS -> QsWifiViewModel(commonImpl)
             }
     }
 }
@@ -64,23 +53,14 @@
  */
 class HomeWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
 
 /** A view model for the wifi icon shown on keyguard (lockscreen). */
 class KeyguardWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
 
 /** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
 class QsWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 7ed56e7..7aeba66 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -88,7 +88,7 @@
     private val chipbarAnimator: ChipbarAnimator,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
-    private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
+    private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler,
     private val viewUtil: ViewUtil,
     private val vibratorHelper: VibratorHelper,
     wakeLockBuilder: WakeLock.Builder,
@@ -289,10 +289,6 @@
     }
 
     private fun updateGestureListening() {
-        if (swipeChipbarAwayGestureHandler == null) {
-            return
-        }
-
         val currentDisplayInfo = getCurrentDisplayInfo()
         if (currentDisplayInfo != null && currentDisplayInfo.info.allowSwipeToDismiss) {
             swipeChipbarAwayGestureHandler.setViewFetcher { currentDisplayInfo.view }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
index 9dbc4b3..80de523 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
@@ -19,10 +19,12 @@
 import android.content.Context
 import android.view.MotionEvent
 import android.view.View
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
 import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
 import com.android.systemui.util.boundsOnScreen
+import javax.inject.Inject
 
 /**
  * A class to detect when a user has swiped the chipbar away.
@@ -30,7 +32,10 @@
  * Effectively [SysUISingleton]. But, this shouldn't be created if the gesture isn't enabled. See
  * [TemporaryDisplayModule.provideSwipeChipbarAwayGestureHandler].
  */
-class SwipeChipbarAwayGestureHandler(
+@SysUISingleton
+class SwipeChipbarAwayGestureHandler
+@Inject
+constructor(
     context: Context,
     displayTracker: DisplayTracker,
     logger: SwipeUpGestureLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
index cae1308..2d05573 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -16,14 +16,9 @@
 
 package com.android.systemui.temporarydisplay.dagger
 
-import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
-import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
 import dagger.Module
 import dagger.Provides
 
@@ -36,20 +31,5 @@
         fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create("ChipbarLog", 40)
         }
-
-        @Provides
-        @SysUISingleton
-        fun provideSwipeChipbarAwayGestureHandler(
-            mediaTttFlags: MediaTttFlags,
-            context: Context,
-            displayTracker: DisplayTracker,
-            logger: SwipeUpGestureLogger,
-        ): SwipeChipbarAwayGestureHandler? {
-            return if (mediaTttFlags.isMediaTttDismissGestureEnabled()) {
-                SwipeChipbarAwayGestureHandler(context, displayTracker, logger)
-            } else {
-                null
-            }
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 5d09e06..a501e87 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -20,6 +20,11 @@
 
 import com.android.systemui.Dependency;
 
+/**
+ * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead
+ * or {@code SettingsObserver} to be able to specify the handler.
+ */
+@Deprecated
 public abstract class TunerService {
 
     public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 8cfe2ea..ccc0a79 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -56,7 +56,11 @@
 
 
 /**
+ * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead
+ * or {@code SettingsObserver} to be able to specify the handler.
+ * This class will interact with SecureSettings using the main looper.
  */
+@Deprecated
 @SysUISingleton
 public class TunerServiceImpl extends TunerService {
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a5365fb..2acd4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -621,13 +621,9 @@
     }
 
     private boolean isDismissableFromBubbles(NotificationEntry e) {
-        if (mNotifPipelineFlags.allowDismissOngoing()) {
-            // Bubbles are only accessible from the unlocked state,
-            // so we can calculate this from the Notification flags only.
-            return e.isDismissableForState(/*isLocked=*/ false);
-        } else {
-            return e.legacyIsDismissableRecursive();
-        }
+        // Bubbles are only accessible from the unlocked state,
+        // so we can calculate this from the Notification flags only.
+        return e.isDismissableForState(/*isLocked=*/ false);
     }
 
     private boolean shouldBubbleUp(NotificationEntry e) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 512e5dc..7114c22 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -27,6 +27,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockConfig;
 import com.android.systemui.plugins.ClockController;
@@ -62,6 +63,8 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private InteractionJankMonitor mInteractionJankMonitor;
 
+    @Mock private DumpManager mDumpManager;
+
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
 
@@ -82,7 +85,8 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor) {
+                mInteractionJankMonitor,
+                mDumpManager) {
                     @Override
                     void setProperty(
                             AnimatableProperty property,
@@ -170,4 +174,12 @@
         verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false);
         verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true);
     }
+
+    @Test
+    public void correctlyDump() {
+        mController.onInit();
+        verify(mDumpManager).registerDumpable(mController);
+        mController.onDestroy();
+        verify(mDumpManager, times(1)).unregisterDumpable(KeyguardStatusViewController.TAG);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index 6decb88..5867a40c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -30,6 +30,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -43,12 +45,14 @@
 @RunWithLooper
 public class ExpandHelperTest extends SysuiTestCase {
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private ExpandableNotificationRow mRow;
     private ExpandHelper mExpandHelper;
     private ExpandHelper.Callback mCallback;
 
     @Before
     public void setUp() throws Exception {
+        mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
         allowTestableLooperAsMainThread();
@@ -56,7 +60,8 @@
         NotificationTestHelper helper = new NotificationTestHelper(
                 mContext,
                 mDependency,
-                TestableLooper.get(this));
+                TestableLooper.get(this),
+                mFeatureFlags);
         mRow = helper.createRow();
         mCallback = mock(ExpandHelper.Callback.class);
         mExpandHelper = new ExpandHelper(context, mCallback, 10, 100);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
new file mode 100644
index 0000000..f9b590f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.BiometricModality
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptHistoryImplTest : SysuiTestCase() {
+
+    private lateinit var history: PromptHistoryImpl
+
+    @Before
+    fun setup() {
+        history = PromptHistoryImpl()
+    }
+
+    @Test
+    fun empty() {
+        assertThat(history.faceFailed).isFalse()
+        assertThat(history.fingerprintFailed).isFalse()
+    }
+
+    @Test
+    fun faceFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.None)
+            history.failure(BiometricModality.Face)
+
+            assertThat(history.faceFailed).isTrue()
+            assertThat(history.fingerprintFailed).isFalse()
+        }
+
+    @Test
+    fun fingerprintFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.None)
+            history.failure(BiometricModality.Fingerprint)
+
+            assertThat(history.faceFailed).isFalse()
+            assertThat(history.fingerprintFailed).isTrue()
+        }
+
+    @Test
+    fun coexFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.Face)
+            history.failure(BiometricModality.Fingerprint)
+
+            assertThat(history.faceFailed).isTrue()
+            assertThat(history.fingerprintFailed).isTrue()
+
+            history.failure(BiometricModality.None)
+        }
+}
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 91140a9..40b1f20 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
@@ -335,7 +335,7 @@
                     error,
                     messageAfterError = "or me",
                     authenticateAfterError = false,
-                    suppressIf = { _ -> true },
+                    suppressIf = { _, _ -> true },
                 )
             }
         }
@@ -364,7 +364,7 @@
                     error,
                     messageAfterError = "$error $afterSuffix",
                     authenticateAfterError = false,
-                    suppressIf = { currentMessage -> suppress && currentMessage.isError },
+                    suppressIf = { currentMessage, _ -> suppress && currentMessage.isError },
                 )
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index 461ec65..40f0ed3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -28,10 +28,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamLogger;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -57,8 +57,6 @@
     private FakeFeatureFlags mFeatureFlags;
     @Mock
     private Observer mObserver;
-    @Mock
-    private DreamLogger mLogger;
 
     @Before
     public void setUp() {
@@ -70,7 +68,7 @@
                 mExecutor,
                 /* overlayEnabled= */ true,
                 mFeatureFlags,
-                mLogger);
+                FakeLogBuffer.Factory.Companion.create());
         mLiveData = new ComplicationCollectionLiveData(mStateController);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index a00e545..57307fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -9,6 +9,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.mockito.argumentCaptor
@@ -47,7 +48,7 @@
     @Mock private lateinit var stateController: DreamOverlayStateController
     @Mock private lateinit var configController: ConfigurationController
     @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
-    @Mock private lateinit var logger: DreamLogger
+    private val logBuffer = FakeLogBuffer.Factory.create()
     private lateinit var controller: DreamOverlayAnimationsController
 
     @Before
@@ -66,7 +67,7 @@
                 DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
                 DREAM_IN_TRANSLATION_Y_DISTANCE,
                 DREAM_IN_TRANSLATION_Y_DURATION,
-                logger
+                logBuffer
             )
 
         val mockView: View = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 2c1ebe4..44a78ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -34,6 +34,8 @@
 import com.android.systemui.complication.Complication;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -58,8 +60,7 @@
     @Mock
     private FeatureFlags mFeatureFlags;
 
-    @Mock
-    private DreamLogger mLogger;
+    private final LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
 
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -408,6 +409,11 @@
     }
 
     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
-        return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger);
+        return new DreamOverlayStateController(
+                mExecutor,
+                overlayEnabled,
+                mFeatureFlags,
+                mLogBuffer
+        );
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 5dc0e55..4e74f451 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -48,6 +48,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -113,8 +115,8 @@
     DreamOverlayStateController mDreamOverlayStateController;
     @Mock
     UserTracker mUserTracker;
-    @Mock
-    DreamLogger mLogger;
+
+    LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
 
     @Captor
     private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -149,7 +151,7 @@
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
                 mUserTracker,
-                mLogger);
+                mLogBuffer);
     }
 
     @Test
@@ -293,7 +295,7 @@
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
                 mUserTracker,
-                mLogger);
+                mLogBuffer);
         controller.onViewAttached();
         verify(mView, never()).showIcon(
                 eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 47c662c..8127ac6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
@@ -385,7 +386,10 @@
 
             detectionCallback.value.onFaceDetected(1, 1, true)
 
-            assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true))
+            val status = detectStatus()!!
+            assertThat(status.sensorId).isEqualTo(1)
+            assertThat(status.userId).isEqualTo(1)
+            assertThat(status.isStrongBiometric).isEqualTo(true)
         }
 
     @Test
@@ -451,6 +455,29 @@
         }
 
     @Test
+    fun multipleCancelCallsShouldNotCauseMultipleCancellationStatusBeingEmitted() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+            val emittedValues by collectValues(underTest.authenticationStatus)
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.cancel()
+            advanceTimeBy(100)
+            underTest.cancel()
+
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+            runCurrent()
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+            runCurrent()
+
+            assertThat(emittedValues.size).isEqualTo(1)
+            assertThat(emittedValues.first())
+                .isInstanceOf(ErrorFaceAuthenticationStatus::class.java)
+            assertThat((emittedValues.first() as ErrorFaceAuthenticationStatus).msgId).isEqualTo(-1)
+        }
+
+    @Test
     fun faceHelpMessagesAreIgnoredBasedOnConfig() =
         testScope.runTest {
             overrideResource(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
new file mode 100644
index 0000000..6e52d1a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -0,0 +1,299 @@
+/*
+ *  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.keyguard.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: OccludingAppDeviceEntryInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var trustRepository: FakeTrustRepository
+
+    @Mock private lateinit var indicationHelper: IndicationHelper
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mockedContext: Context
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+        fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        configurationRepository = FakeConfigurationRepository()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.FACE_AUTH_REFACTOR, false)
+                set(Flags.DELAY_BOUNCER, false)
+            }
+        trustRepository = FakeTrustRepository()
+        underTest =
+            OccludingAppDeviceEntryInteractor(
+                BiometricMessageInteractor(
+                    mContext.resources,
+                    fingerprintAuthRepository,
+                    fingerprintPropertyRepository,
+                    indicationHelper,
+                    keyguardUpdateMonitor,
+                ),
+                fingerprintAuthRepository,
+                KeyguardInteractor(
+                    keyguardRepository,
+                    commandQueue = mock(),
+                    featureFlags,
+                    bouncerRepository,
+                    configurationRepository,
+                ),
+                PrimaryBouncerInteractor(
+                    bouncerRepository,
+                    primaryBouncerView = mock(),
+                    mainHandler = mock(),
+                    keyguardStateController = mock(),
+                    keyguardSecurityModel = mock(),
+                    primaryBouncerCallbackInteractor = mock(),
+                    falsingCollector = mock(),
+                    dismissCallbackRegistry = mock(),
+                    context,
+                    keyguardUpdateMonitor,
+                    trustRepository,
+                    featureFlags,
+                    testScope.backgroundScope,
+                ),
+                AlternateBouncerInteractor(
+                    statusBarStateController = mock(),
+                    keyguardStateController = mock(),
+                    bouncerRepository,
+                    biometricSettingsRepository,
+                    FakeSystemClock(),
+                    keyguardUpdateMonitor,
+                ),
+                testScope.backgroundScope,
+                mockedContext,
+                activityStarter,
+            )
+    }
+
+    @Test
+    fun fingerprintSuccess_goToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyGoToHomeScreen()
+        }
+
+    @Test
+    fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
+    fun lockout_goToHomeScreenOnDismissAction() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "lockoutTest"
+                )
+            )
+            runCurrent()
+            verifyGoToHomeScreenOnDismiss()
+        }
+
+    @Test
+    fun lockout_notOnOccludingApp_neverGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "lockoutTest"
+                )
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
+    fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+
+            givenOnOccludingApp(true)
+            givenPrimaryAuthRequired(false)
+            runCurrent()
+            // WHEN a fp failure come in
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            // THEN message set to failure
+            assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
+
+            // GIVEN fingerprint shouldn't run
+            givenOnOccludingApp(false)
+            runCurrent()
+            // WHEN another fp failure arrives
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN message set to null
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun message_fpErrorHelpFailOnOccludingApp() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+
+            givenOnOccludingApp(true)
+            givenPrimaryAuthRequired(false)
+            runCurrent()
+
+            // ERROR message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "testError",
+                )
+            )
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)
+            assertThat(message?.message).isEqualTo("testError")
+            assertThat(message?.type).isEqualTo(BiometricMessageType.ERROR)
+
+            // HELP message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                HelpFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+                    "testHelp",
+                )
+            )
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL)
+            assertThat(message?.message).isEqualTo("testHelp")
+            assertThat(message?.type).isEqualTo(BiometricMessageType.HELP)
+
+            // FAIL message
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id)
+                .isEqualTo(KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+            assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
+        }
+
+    private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+        keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
+        keyguardRepository.setKeyguardShowing(isOnOccludingApp)
+        bouncerRepository.setPrimaryShow(!isOnOccludingApp)
+        bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+    }
+
+    private fun givenPrimaryAuthRequired(required: Boolean) {
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+            .thenReturn(!required)
+    }
+
+    private fun verifyGoToHomeScreen() {
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(mockedContext).startActivity(intentCaptor.capture())
+
+        assertThat(intentCaptor.value.hasCategory(Intent.CATEGORY_HOME)).isTrue()
+        assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_MAIN)
+    }
+
+    private fun verifyNeverGoToHomeScreen() {
+        verify(mockedContext, never()).startActivity(any())
+        verify(activityStarter, never())
+            .dismissKeyguardThenExecute(any(OnDismissAction::class.java), isNull(), eq(false))
+    }
+
+    private fun verifyGoToHomeScreenOnDismiss() {
+        val onDimissActionCaptor = ArgumentCaptor.forClass(OnDismissAction::class.java)
+        verify(activityStarter)
+            .dismissKeyguardThenExecute(onDimissActionCaptor.capture(), isNull(), eq(false))
+        onDimissActionCaptor.value.onDismiss()
+
+        verifyGoToHomeScreen()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
new file mode 100644
index 0000000..272d686
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.mockito.Mockito.anyString
+
+/**
+ * A fake [LogBuffer] used for testing that obtains a real [LogMessage] to prevent a
+ * [NullPointerException].
+ */
+class FakeLogBuffer private constructor() {
+    class Factory private constructor() {
+        companion object {
+            fun create(): LogBuffer {
+                val logBuffer = mock<LogBuffer>()
+                whenever(
+                        logBuffer.obtain(
+                            tag = anyString(),
+                            level = any(),
+                            messagePrinter = any(),
+                            exception = nullable(),
+                        )
+                    )
+                    .thenReturn(LogMessageImpl.Factory.create())
+                return logBuffer
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index f79c53d..ab24c46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -63,7 +63,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -124,7 +123,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index f8971fd..45e8e27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -70,7 +70,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -126,7 +125,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 9f06b5f..a59ea20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -93,7 +93,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -197,7 +196,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
@@ -279,7 +278,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -309,7 +308,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -530,7 +529,7 @@
                 "",
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -553,7 +552,7 @@
                 "",
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -589,7 +588,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
@@ -606,7 +605,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
@@ -888,7 +887,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
@@ -1080,7 +1079,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index a14ff2f..3e69a29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -67,7 +67,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 import java.util.function.Consumer;
 
 @MediumTest
@@ -132,7 +131,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = makeTestDialog(mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index 01ffdcd..ee3b80a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -26,6 +26,7 @@
 import android.view.WindowManager
 import android.view.WindowMetrics
 import androidx.core.view.WindowInsetsCompat.Type
+import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
@@ -41,9 +42,10 @@
 @SmallTest
 class TaskPreviewSizeProviderTest : SysuiTestCase() {
 
-    private val mockContext: Context = mock()
-    private val resources: Resources = mock()
-    private val windowManager: WindowManager = mock()
+    private val lifecycleOwner = mock<LifecycleOwner>()
+    private val mockContext = mock<Context>()
+    private val resources = mock<Resources>()
+    private val windowManager = mock<WindowManager>()
     private val sizeUpdates = arrayListOf<Rect>()
     private val testConfigurationController = FakeConfigurationController()
 
@@ -76,7 +78,7 @@
     @Test
     fun size_phoneDisplayAndRotate_emitsSizeUpdate() {
         givenDisplay(width = 400, height = 600, isTablet = false)
-        createSizeProvider()
+        createSizeProvider().also { it.onCreate(lifecycleOwner) }
 
         givenDisplay(width = 600, height = 400, isTablet = false)
         testConfigurationController.onConfigurationChanged(Configuration())
@@ -87,7 +89,7 @@
     @Test
     fun size_phoneDisplayAndRotateConfigurationChange_returnsUpdatedSize() {
         givenDisplay(width = 400, height = 600, isTablet = false)
-        val sizeProvider = createSizeProvider()
+        val sizeProvider = createSizeProvider().also { it.onCreate(lifecycleOwner) }
 
         givenDisplay(width = 600, height = 400, isTablet = false)
         testConfigurationController.onConfigurationChanged(Configuration())
@@ -95,6 +97,20 @@
         assertThat(sizeProvider.size).isEqualTo(Rect(0, 0, 150, 100))
     }
 
+    @Test
+    fun size_phoneDisplayAndRotateConfigurationChange_afterChooserDestroyed_doesNotUpdate() {
+        givenDisplay(width = 400, height = 600, isTablet = false)
+        val sizeProvider = createSizeProvider()
+        val previousSize = Rect(sizeProvider.size)
+
+        sizeProvider.onCreate(lifecycleOwner)
+        sizeProvider.onDestroy(lifecycleOwner)
+        givenDisplay(width = 600, height = 400, isTablet = false)
+        testConfigurationController.onConfigurationChanged(Configuration())
+
+        assertThat(sizeProvider.size).isEqualTo(previousSize)
+    }
+
     private fun givenTaskbarSize(size: Int) {
         val windowInsets =
             WindowInsets.Builder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 9188293..ecd3308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -370,7 +370,8 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor));
+                mInteractionJankMonitor,
+                mDumpManager));
 
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
new file mode 100644
index 0000000..3c9db8f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.shadow
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.testing.AndroidTestingRunner
+import android.util.AttributeSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.shadow.DoubleShadowTextClock
+import com.android.systemui.shared.shadow.ResourcesProvider
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DoubleShadowTextClockTest : SysuiTestCase() {
+    @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock lateinit var resourcesProvider: ResourcesProvider
+
+    @Mock lateinit var attributes: TypedArray
+
+    private lateinit var context: Context
+    private var attrs: AttributeSet? = null
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        context = getContext()
+        whenever(attributes.getBoolean(R.styleable.DoubleShadowTextClock_removeTextDescent, false))
+            .thenReturn(true)
+    }
+
+    @Test
+    fun testAddingPaddingToBottomOfClockWhenConfigIsTrue() {
+        whenever(
+                resourcesProvider.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding)
+            )
+            .thenReturn(true)
+
+        val doubleShadowTextClock =
+            DoubleShadowTextClock(
+                resourcesProvider = resourcesProvider,
+                context = context,
+                attrs = attrs,
+                attributesInput = attributes
+            )
+        assertTrue(doubleShadowTextClock.paddingBottom > 0)
+    }
+
+    @Test
+    fun testRemovingPaddingToBottomOfClockWhenConfigIsFalse() {
+        whenever(
+                resourcesProvider.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding)
+            )
+            .thenReturn(false)
+
+        val doubleShadowTextClock =
+            DoubleShadowTextClock(
+                resourcesProvider = resourcesProvider,
+                context = context,
+                attrs = attrs,
+                attributesInput = attributes
+            )
+        assertTrue(doubleShadowTextClock.paddingBottom < 0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
index ed24947..f91e5a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -47,14 +46,11 @@
     private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener
     private val keyguardStateController: KeyguardStateController = mock()
     private val pipeline: NotifPipeline = mock()
-    private val flags: NotifPipelineFlags = mock()
     private val dumpManager: DumpManager = mock()
 
     @Before
     fun setUp() {
-        whenever(flags.allowDismissOngoing()).thenReturn(true)
-
-        dismissibilityProvider = NotificationDismissibilityProviderImpl(flags, dumpManager)
+        dismissibilityProvider = NotificationDismissibilityProviderImpl(dumpManager)
         coordinator = DismissibilityCoordinator(keyguardStateController, dismissibilityProvider)
         coordinator.attach(pipeline)
         onBeforeRenderListListener = withArgCaptor {
@@ -309,57 +305,4 @@
             dismissibilityProvider.isDismissable(summary)
         )
     }
-
-    @Test
-    fun testFeatureToggleOffNonDismissibleEntry() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertTrue(
-            "FLAG_NO_DISMISS should be ignored, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
-
-    @Test
-    fun testFeatureToggleOffOngoingNotifWhenPhoneIsLocked() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(false)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertFalse(
-            "Ongoing Notifs should NOT be dismissible, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
-
-    @Test
-    fun testFeatureToggleOffOngoingNotifWhenPhoneIsUnLocked() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(true)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertFalse(
-            "Ongoing Notifs should NOT be dismissible, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 608778e..1dc8453 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -87,6 +88,7 @@
 @RunWithLooper
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private NotificationTestHelper mNotificationTestHelper;
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
 
@@ -96,12 +98,10 @@
         mNotificationTestHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
-                TestableLooper.get(this));
+                TestableLooper.get(this),
+                mFeatureFlags);
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
-
-        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
-        fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
-        mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
+        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
     }
 
     @Test
@@ -183,6 +183,14 @@
     }
 
     @Test
+    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue()
+            throws Exception {
+        FakeFeatureFlags flags = mFeatureFlags;
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
+        testSetSensitiveOnNotifRowNotifiesOfHeightChange();
+    }
+
+    @Test
     public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
         // GIVEN a sensitive notification row that's currently redacted
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -199,10 +207,19 @@
         // WHEN the row is set to no longer be sensitive
         row.setSensitive(false, true);
 
+        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
         assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(row), eq(false));
+        verify(listener).onHeightChanged(eq(row), eq(expectAnimation));
+    }
+
+    @Test
+    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue()
+            throws Exception {
+        FakeFeatureFlags flags = mFeatureFlags;
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
+        testSetSensitiveOnGroupRowNotifiesOfHeightChange();
     }
 
     @Test
@@ -222,10 +239,19 @@
         // WHEN the row is set to no longer be sensitive
         group.setSensitive(false, true);
 
+        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
         assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(group), eq(false));
+        verify(listener).onHeightChanged(eq(group), eq(expectAnimation));
+    }
+
+    @Test
+    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue()
+            throws Exception {
+        FakeFeatureFlags flags = mFeatureFlags;
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
+        testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange();
     }
 
     @Test
@@ -254,7 +280,7 @@
         assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
         assertThat(publicRow.getPrivateLayout().getMinHeight())
                 .isEqualTo(publicRow.getPublicLayout().getMinHeight());
-        verify(listener, never()).onHeightChanged(eq(publicRow), eq(false));
+        verify(listener, never()).onHeightChanged(eq(publicRow), anyBoolean());
     }
 
     private void measureAndLayout(ExpandableNotificationRow row) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 1a644d3..d21029d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -48,12 +48,16 @@
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -90,6 +94,7 @@
 
 import org.mockito.ArgumentCaptor;
 
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -130,14 +135,24 @@
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     public final Runnable mFutureDismissalRunnable;
     private @InflationFlag int mDefaultInflationFlags;
-    private FeatureFlags mFeatureFlags;
+    private final FakeFeatureFlags mFeatureFlags;
 
     public NotificationTestHelper(
             Context context,
             TestableDependency dependency,
             TestableLooper testLooper) {
+        this(context, dependency, testLooper, new FakeFeatureFlags());
+    }
+
+    public NotificationTestHelper(
+            Context context,
+            TestableDependency dependency,
+            TestableLooper testLooper,
+            @NonNull FakeFeatureFlags featureFlags) {
         mContext = context;
         mTestLooper = testLooper;
+        mFeatureFlags = Objects.requireNonNull(featureFlags);
+        dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         dependency.injectMockDependency(MediaOutputDialogFactory.class);
@@ -183,17 +198,12 @@
         mFutureDismissalRunnable = mock(Runnable.class);
         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
                 .thenReturn(mFutureDismissalRunnable);
-        mFeatureFlags = mock(FeatureFlags.class);
     }
 
     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
         mDefaultInflationFlags = defaultInflationFlags;
     }
 
-    public void setFeatureFlags(FeatureFlags featureFlags) {
-        mFeatureFlags = featureFlags;
-    }
-
     public ExpandableNotificationRowLogger getMockLogger() {
         return mMockLogger;
     }
@@ -527,6 +537,10 @@
             @InflationFlag int extraInflationFlags,
             int importance)
             throws Exception {
+        // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
+        //  set, but we do not want to override an existing value that is needed by a specific test.
+        mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);
+
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 mContext.LAYOUT_INFLATER_SERVICE);
         mRow = (ExpandableNotificationRow) inflater.inflate(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 09382ec..3d75288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -42,7 +41,6 @@
     private val bypassController = StackScrollAlgorithm.BypassController { false }
     private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
     private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
-    private val featureFlags = mock<FeatureFlags>()
 
     private lateinit var sut: AmbientState
 
@@ -55,8 +53,7 @@
                 sectionProvider,
                 bypassController,
                 statusBarKeyguardViewManager,
-                largeScreenShadeInterpolator,
-                featureFlags
+                largeScreenShadeInterpolator
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index f38881c..4b145d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,7 +30,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -65,7 +64,6 @@
     @Mock private SectionHeaderController mPeopleHeaderController;
     @Mock private SectionHeaderController mAlertingHeaderController;
     @Mock private SectionHeaderController mSilentHeaderController;
-    @Mock private FeatureFlags mFeatureFlag;
 
     private NotificationSectionsManager mSectionsManager;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 8d751e3..1dc0ab0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -9,7 +9,9 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarIconView
@@ -20,6 +22,7 @@
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
+import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,22 +37,32 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
-class NotificationShelfTest : SysuiTestCase() {
+open class NotificationShelfTest : SysuiTestCase() {
+
+    open val useShelfRefactor: Boolean = false
+    open val useSensitiveReveal: Boolean = false
+    private val flags = FakeFeatureFlags()
 
     @Mock
     private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
     @Mock
-    private lateinit var flags: FeatureFlags
-    @Mock
     private lateinit var ambientState: AmbientState
     @Mock
     private lateinit var hostLayoutController: NotificationStackScrollLayoutController
+    @Mock
+    private lateinit var hostLayout: NotificationStackScrollLayout
+    @Mock
+    private lateinit var roundnessManager: NotificationRoundnessManager
 
     private lateinit var shelf: NotificationShelf
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        mDependency.injectTestDependency(FeatureFlags::class.java, flags)
+        flags.set(Flags.NOTIFICATION_SHELF_REFACTOR, useShelfRefactor)
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
+        flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS)
         val root = FrameLayout(context)
         shelf = LayoutInflater.from(root.context)
                 .inflate(/* resource = */ R.layout.status_bar_notification_shelf,
@@ -57,10 +70,13 @@
                     /* attachToRoot = */false) as NotificationShelf
 
         whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
-        whenever(ambientState.featureFlags).thenReturn(flags)
         whenever(ambientState.isSmallScreen).thenReturn(true)
 
-        shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController)
+        if (useShelfRefactor) {
+            shelf.bind(ambientState, hostLayout, roundnessManager)
+        } else {
+            shelf.bind(ambientState, hostLayoutController)
+        }
         shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
     }
 
@@ -345,7 +361,7 @@
     @Test
     fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
         // GIVEN
-        shelf.setSensitiveRevealAnimEnabled(true)
+        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -372,7 +388,7 @@
     @Test
     fun updateState_withNullFirstViewInShelf_hideShelf() {
         // GIVEN
-        shelf.setSensitiveRevealAnimEnabled(true)
+        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -399,7 +415,7 @@
     @Test
     fun updateState_withCollapsedShade_hideShelf() {
         // GIVEN
-        shelf.setSensitiveRevealAnimEnabled(true)
+        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -426,7 +442,7 @@
     @Test
     fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
         // GIVEN
-        shelf.setSensitiveRevealAnimEnabled(true)
+        assumeTrue(useSensitiveReveal)
         whenever(ambientState.stackY).thenReturn(100f)
         whenever(ambientState.stackHeight).thenReturn(100f)
         val paddingBetweenElements =
@@ -486,3 +502,25 @@
         assertEquals(expectedAlpha, shelf.viewState.alpha)
     }
 }
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfWithRefactorTest : NotificationShelfTest() {
+    override val useShelfRefactor: Boolean = true
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() {
+    override val useSensitiveReveal: Boolean = true
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfWithBothFlagsTest : NotificationShelfTest() {
+    override val useShelfRefactor: Boolean = true
+    override val useSensitiveReveal: Boolean = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index ee8325e..07eadf7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -54,7 +54,6 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -119,6 +118,7 @@
 @RunWith(AndroidTestingRunner.class)
 public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock private NotificationGutsManager mNotificationGutsManager;
     @Mock private NotificationsController mNotificationsController;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -157,7 +157,6 @@
     @Mock private StackStateLogger mStackLogger;
     @Mock private NotificationStackScrollLogger mLogger;
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
     @Mock private SecureSettings mSecureSettings;
     @Mock private NotificationIconAreaController mIconAreaController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 8ad271b..72fcdec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -68,7 +68,9 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -106,6 +108,7 @@
 @TestableLooper.RunWithLooper
 public class NotificationStackScrollLayoutTest extends SysuiTestCase {
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private NotificationStackScrollLayout mStackScroller;  // Normally test this
     private NotificationStackScrollLayout mStackScrollerInternal;  // See explanation below
     private AmbientState mAmbientState;
@@ -129,7 +132,6 @@
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     public void setUp() throws Exception {
@@ -143,11 +145,25 @@
                 mNotificationSectionsManager,
                 mBypassController,
                 mStatusBarKeyguardViewManager,
-                mLargeScreenShadeInterpolator,
-                mFeatureFlags
+                mLargeScreenShadeInterpolator
         ));
 
+        // Register the debug flags we use
+        assertFalse(Flags.NSSL_DEBUG_LINES.getDefault());
+        assertFalse(Flags.NSSL_DEBUG_REMOVE_ANIMATION.getDefault());
+        mFeatureFlags.set(Flags.NSSL_DEBUG_LINES, false);
+        mFeatureFlags.set(Flags.NSSL_DEBUG_REMOVE_ANIMATION, false);
+
+        // Register the feature flags we use
+        // TODO: Ideally we wouldn't need to set these unless a test actually reads them,
+        //  and then we would test both configurations, but currently they are all read
+        //  in the constructor.
+        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
+        mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
+        mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR);
+
         // Inject dependencies before initializing the layout
+        mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
         mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectTestDependency(
@@ -176,13 +192,18 @@
         mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setShelfController(notificationShelfController);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            mStackScroller.setShelfController(notificationShelfController);
+        }
         mStackScroller.setNotificationsController(mNotificationsController);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
                 .thenReturn(mNotificationRoundnessManager);
         mStackScroller.setController(mStackScrollLayoutController);
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            mStackScroller.setShelf(mNotificationShelf);
+        }
 
         doNothing().when(mGroupExpansionManager).collapseGroups();
         doNothing().when(mExpandHelper).cancelImmediately();
@@ -899,7 +920,6 @@
     @Test
     public void testWindowInsetAnimationProgress_updatesBottomInset() {
         int bottomImeInset = 100;
-        mStackScrollerInternal.setAnimatedInsetsEnabled(true);
         WindowInsets windowInsets = new WindowInsets.Builder()
                 .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build();
         ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index df65c09..85a2bdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -47,6 +47,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
@@ -83,7 +84,7 @@
     private Handler mHandler;
     private ExpandableNotificationRow mNotificationRow;
     private Runnable mFalsingCheck;
-    private FeatureFlags mFeatureFlags;
+    private final FeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     private static final int FAKE_ROW_WIDTH = 20;
     private static final int FAKE_ROW_HEIGHT = 20;
@@ -96,7 +97,6 @@
         mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
         mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
         mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
-        mFeatureFlags = mock(FeatureFlags.class);
         mSwipeHelper = spy(new NotificationSwipeHelper(
                 mContext.getResources(),
                 ViewConfiguration.get(mContext),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index 45725ce..e30947c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -18,6 +18,7 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class NotificationTargetsHelperTest : SysuiTestCase() {
+    private val featureFlags = FakeFeatureFlags()
     lateinit var notificationTestHelper: NotificationTestHelper
     private val sectionsManager: NotificationSectionsManager = mock()
     private val stackScrollLayout: NotificationStackScrollLayout = mock()
@@ -26,10 +27,10 @@
     fun setUp() {
         allowTestableLooperAsMainThread()
         notificationTestHelper =
-            NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+            NotificationTestHelper(mContext, mDependency, TestableLooper.get(this), featureFlags)
     }
 
-    private fun notificationTargetsHelper() = NotificationTargetsHelper(FakeFeatureFlags())
+    private fun notificationTargetsHelper() = NotificationTargetsHelper(featureFlags)
 
     @Test
     fun targetsForFirstNotificationInGroup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4c97d20..987861d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -8,7 +8,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.EmptyShadeView
 import com.android.systemui.statusbar.NotificationShelf
@@ -45,7 +44,6 @@
     private val dumpManager = mock<DumpManager>()
     private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
     private val notificationShelf = mock<NotificationShelf>()
-    private val featureFlags = mock<FeatureFlags>()
     private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
         layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
     }
@@ -56,7 +54,6 @@
             /* bypassController */ { false },
             mStatusBarKeyguardViewManager,
             largeScreenShadeInterpolator,
-            featureFlags,
         )
 
     private val testableResources = mContext.getOrCreateTestableResources()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 9157cd9..085ec27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -15,7 +15,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 
 import static junit.framework.Assert.assertTrue;
 
@@ -39,14 +38,13 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -57,23 +55,27 @@
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidTestingRunner.class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class StatusBarIconControllerTest extends LeakCheckedTest {
 
     private MobileContextProvider mMobileContextProvider = mock(MobileContextProvider.class);
+    private MobileUiAdapter mMobileUiAdapter = mock(MobileUiAdapter.class);
+    private MobileIconsViewModel mMobileIconsViewModel = mock(MobileIconsViewModel.class);
 
     @Before
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         // For testing, ignore context overrides
         when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
+        when(mMobileUiAdapter.getMobileIconsViewModel()).thenReturn(mMobileIconsViewModel);
     }
 
     @Test
     public void testSetCalledOnAdd_IconManager() {
         LinearLayout layout = new LinearLayout(mContext);
-        TestIconManager manager = new TestIconManager(layout, mMobileContextProvider);
+        TestIconManager manager =
+                new TestIconManager(layout, mMobileUiAdapter, mMobileContextProvider);
         testCallOnAdd_forManager(manager);
     }
 
@@ -83,9 +85,8 @@
         TestDarkIconManager manager = new TestDarkIconManager(
                 layout,
                 StatusBarLocation.HOME,
-                mock(StatusBarPipelineFlags.class),
                 mock(WifiUiAdapter.class),
-                mock(MobileUiAdapter.class),
+                mMobileUiAdapter,
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
@@ -153,15 +154,10 @@
         assertTrue("Expected StatusBarIconView",
                 (manager.getViewAt(0) instanceof StatusBarIconView));
 
-        holder = holderForType(TYPE_MOBILE);
-        manager.onIconAdded(1, "test_mobile", false, holder);
-        assertTrue(manager.getViewAt(1) instanceof StatusBarMobileView);
     }
 
     private StatusBarIconHolder holderForType(int type) {
         switch (type) {
-            case TYPE_MOBILE:
-                return StatusBarIconHolder.fromMobileIconState(mock(MobileIconState.class));
 
             case TYPE_ICON:
             default:
@@ -175,14 +171,12 @@
         TestDarkIconManager(
                 LinearLayout group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     contextProvider,
@@ -202,23 +196,18 @@
 
             return mock;
         }
-
-        @Override
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
-            StatusBarMobileView mock = mock(StatusBarMobileView.class);
-            mGroup.addView(mock, index);
-
-            return mock;
-        }
     }
 
     private static class TestIconManager extends IconManager implements TestableIconManager {
-        TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
+        TestIconManager(
+                ViewGroup group,
+                MobileUiAdapter adapter,
+                MobileContextProvider contextProvider
+        ) {
             super(group,
                     StatusBarLocation.HOME,
-                    mock(StatusBarPipelineFlags.class),
                     mock(WifiUiAdapter.class),
-                    mock(MobileUiAdapter.class),
+                    adapter,
                     contextProvider);
         }
 
@@ -235,14 +224,6 @@
 
             return mock;
         }
-
-        @Override
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
-            StatusBarMobileView mock = mock(StatusBarMobileView.class);
-            mGroup.addView(mock, index);
-
-            return mock;
-        }
     }
 
     private interface TestableIconManager {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 9c7f619..33144f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -64,7 +64,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -117,6 +117,7 @@
 public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
 
     private static final int DISPLAY_ID = 0;
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Mock
     private AssistManager mAssistManager;
@@ -256,7 +257,7 @@
                         notificationAnimationProvider,
                         mock(LaunchFullScreenIntentProvider.class),
                         mPowerInteractor,
-                        mock(FeatureFlags.class),
+                        mFeatureFlags,
                         mUserTracker
                 );
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index cd8aaa2..9c52788 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -79,7 +79,6 @@
     private CommandQueue mCommandQueue;
     private FakeMetricsLogger mMetricsLogger;
     private final ShadeController mShadeController = mock(ShadeController.class);
-    private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
     private final NotificationsInteractor mNotificationsInteractor =
             mock(NotificationsInteractor.class);
     private final KeyguardStateController mKeyguardStateController =
@@ -118,7 +117,6 @@
                 mock(NotificationShadeWindowController.class),
                 mock(DynamicPrivacyController.class),
                 mKeyguardStateController,
-                mCentralSurfaces,
                 mNotificationsInteractor,
                 mock(LockscreenShadeTransitionController.class),
                 mock(PowerInteractor.class),
@@ -202,7 +200,6 @@
 
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isOccluded()).thenReturn(false);
-        when(mCentralSurfaces.isOccluded()).thenReturn(false);
         assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
index 4aa48d6..755aaa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
@@ -54,7 +54,7 @@
     @Test
     fun collectionStarted_dumpHasInfo() {
         val view = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
 
@@ -66,8 +66,8 @@
     fun collectionStarted_multipleViews_dumpHasInfo() {
         val view = TextView(context)
         val view2 = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
-        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
+        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
         underTest.logCollectionStarted(view2, viewModel2)
@@ -81,8 +81,8 @@
     fun collectionStopped_dumpHasInfo() {
         val view = TextView(context)
         val view2 = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
-        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
+        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
         underTest.logCollectionStarted(view2, viewModel2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 7420db2..59fc0ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -235,7 +235,6 @@
 
     @Test
     fun onDarkChanged_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view =
             ModernStatusBarMobileView.constructAndBind(
                 context,
@@ -257,7 +256,6 @@
 
     @Test
     fun setStaticDrawableColor_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view =
             ModernStatusBarMobileView.constructAndBind(
                 context,
@@ -298,7 +296,7 @@
                 constants,
                 testScope.backgroundScope,
             )
-        viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+        viewModel = QsMobileIconViewModel(viewModelCommon)
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index d5fb577..e59d90f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -86,9 +86,9 @@
                 testScope.backgroundScope,
             )
 
-        homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock())
-        qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
-        keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+        homeIcon = HomeMobileIconViewModel(commonImpl, mock())
+        qsIcon = QsMobileIconViewModel(commonImpl)
+        keyguardIcon = KeyguardMobileIconViewModel(commonImpl)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 0d51af2..3f49935 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,7 +45,6 @@
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -64,7 +62,6 @@
 
     private lateinit var testableLooper: TestableLooper
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
@@ -110,7 +107,6 @@
         viewModel =
             viewModelForLocation(
                 viewModelCommon,
-                statusBarPipelineFlags,
                 StatusBarLocation.HOME,
             )
     }
@@ -199,7 +195,6 @@
 
     @Test
     fun onDarkChanged_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
@@ -215,7 +210,6 @@
 
     @Test
     fun setStaticDrawableColor_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 0e303b2..cb469ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -58,7 +57,6 @@
 
     private lateinit var underTest: WifiViewModel
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
@@ -107,11 +105,9 @@
     @Test
     fun wifiIcon_allLocationViewModelsReceiveSameData() =
         runBlocking(IMMEDIATE) {
-            val home =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.HOME)
-            val keyguard =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.KEYGUARD)
-            val qs = viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.QS)
+            val home = viewModelForLocation(underTest, StatusBarLocation.HOME)
+            val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
+            val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
 
             var latestHome: WifiIcon? = null
             val jobHome = home.wifiIcon.onEach { latestHome = it }.launchIn(this)
@@ -249,11 +245,9 @@
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            val home =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.HOME)
-            val keyguard =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.KEYGUARD)
-            val qs = viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.QS)
+            val home = viewModelForLocation(underTest, StatusBarLocation.HOME)
+            val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
+            val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
 
             var latestHome: Boolean? = null
             val jobHome = home.isActivityInViewVisible.onEach { latestHome = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 391c8ca..7c285b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -102,6 +102,8 @@
             "com.android.sysuitest.dummynotificationsender";
     private static final int DUMMY_MESSAGE_APP_ID = Process.LAST_APPLICATION_UID - 1;
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+
     @Mock private RemoteInputController mController;
     @Mock private ShortcutManager mShortcutManager;
     @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
@@ -453,8 +455,7 @@
     private RemoteInputViewController bindController(
             RemoteInputView view,
             NotificationEntry entry) {
-        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
-        fakeFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
+        mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
         RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
                 view,
                 entry,
@@ -462,7 +463,7 @@
                 mController,
                 mShortcutManager,
                 mUiEventLoggerFake,
-                fakeFeatureFlags
+                mFeatureFlags
                 );
         viewController.bind();
         return viewController;
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 4839eeb..820e2a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -92,7 +92,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -305,6 +305,7 @@
     private TestableLooper mTestableLooper;
 
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     private UserHandle mUser0;
 
@@ -423,7 +424,7 @@
                 mCommonNotifCollection,
                 mNotifPipeline,
                 mSysUiState,
-                mock(FeatureFlags.class),
+                mFeatureFlags,
                 mNotifPipelineFlags,
                 syncExecutor);
         mBubblesManager.addNotifCallback(mNotifCallback);
@@ -432,7 +433,8 @@
         mNotificationTestHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
-                TestableLooper.get(this));
+                TestableLooper.get(this),
+                mFeatureFlags);
         mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
@@ -1844,8 +1846,7 @@
     }
 
     @Test
-    public void testCreateBubbleFromOngoingNotification_OngoingDismissalEnabled() {
-        when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
+    public void testCreateBubbleFromOngoingNotification() {
         NotificationEntry notif = new NotificationEntryBuilder()
                 .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
                 .setCanBubble(true)
@@ -1858,8 +1859,7 @@
 
 
     @Test
-    public void testCreateBubbleFromNoDismissNotification_OngoingDismissalEnabled() {
-        when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
+    public void testCreateBubbleFromNoDismissNotification() {
         NotificationEntry notif = new NotificationEntryBuilder()
                 .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
                 .setCanBubble(true)
@@ -1871,37 +1871,6 @@
     }
 
     @Test
-    public void testCreateBubbleFromOngoingNotification_OngoingDismissalDisabled() {
-        NotificationEntry notif = new NotificationEntryBuilder()
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .setCanBubble(true)
-                .build();
-
-        BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
-
-        assertFalse(
-                "Ongoing Notifis should be dismissable, if the feature is off",
-                bubble.isDismissable()
-        );
-    }
-
-
-    @Test
-    public void testCreateBubbleFromNoDismissNotification_OngoingDismissalDisabled() {
-        NotificationEntry notif = new NotificationEntryBuilder()
-                .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
-                .setCanBubble(true)
-                .build();
-
-        BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
-
-        assertTrue(
-                "FLAG_NO_DISMISS should be ignored, if the feature is off",
-                bubble.isDismissable()
-        );
-    }
-
-    @Test
     public void registerBubbleBarListener_barDisabled_largeScreen_shouldBeIgnored() {
         mBubbleProperties.mIsBubbleBarEnabled = false;
         mPositioner.setIsLargeScreen(true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index b94f816e..36fa7e6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -62,6 +62,32 @@
         }
     }
 
+    /**
+     * Set the given flag's default value if no other value has been set.
+     *
+     * REMINDER: You should always test your code with your flag in both configurations, so
+     *  generally you should be setting a particular value.  This method should be reserved for
+     *  situations where the flag needs to be read (e.g. in the class constructor), but its
+     *  value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
+     *  this method than to hard-code `false` or `true` because then at least if you're wrong,
+     *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
+     *  start failing.
+     */
+    fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default)
+
+    /**
+     * Set the given flag's default value if no other value has been set.
+     *
+     * REMINDER: You should always test your code with your flag in both configurations, so
+     *  generally you should be setting a particular value.  This method should be reserved for
+     *  situations where the flag needs to be read (e.g. in the class constructor), but its
+     *  value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
+     *  this method than to hard-code `false` or `true` because then at least if you're wrong,
+     *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
+     *  start failing.
+     */
+    fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default)
+
     private fun notifyFlagChanged(flag: Flag<*>) {
         flagListeners[flag.id]?.let { listeners ->
             listeners.forEach { listener ->
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 56837e8..03e3423 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -20,7 +20,6 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 
 import java.util.List;
 
@@ -65,10 +64,6 @@
     }
 
     @Override
-    public void setMobileIcons(String slot, List<MobileIconState> states) {
-    }
-
-    @Override
     public void setNewMobileIconSubIds(List<Integer> subIds) {
     }
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index ca1ab9b..315972c 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -42,6 +42,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.assist.ActivityId;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
@@ -94,10 +95,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.infra.GlobalWhitelistState;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionConsentManager;
 import com.android.server.contentprotection.ContentProtectionPackageManager;
 import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.infra.AbstractMasterSystemService;
@@ -216,6 +219,8 @@
 
     @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
 
+    @Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
+
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
@@ -260,12 +265,15 @@
                 mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
                 mContentProtectionBlocklistManager.updateBlocklist(
                         mDevCfgContentProtectionAppsBlocklistSize);
+                mContentProtectionConsentManager = createContentProtectionConsentManager();
             } else {
                 mContentProtectionBlocklistManager = null;
+                mContentProtectionConsentManager = null;
             }
         } else {
             mContentProtectionServiceComponentName = null;
             mContentProtectionBlocklistManager = null;
+            mContentProtectionConsentManager = null;
         }
     }
 
@@ -802,6 +810,17 @@
                 new ContentProtectionPackageManager(getContext()));
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentProtectionConsentManager createContentProtectionConsentManager() {
+        // Same handler as used by AbstractMasterSystemService
+        return new ContentProtectionConsentManager(
+                BackgroundThread.getHandler(),
+                getContext().getContentResolver(),
+                LocalServices.getService(DevicePolicyManagerInternal.class));
+    }
+
     @Nullable
     private ComponentName getContentProtectionServiceComponentName() {
         String flatComponentName = getContentProtectionServiceFlatComponentName();
@@ -1213,7 +1232,7 @@
                 isContentCaptureReceiverEnabled =
                         isContentCaptureReceiverEnabled(userId, packageName);
                 isContentProtectionReceiverEnabled =
-                        isContentProtectionReceiverEnabled(packageName);
+                        isContentProtectionReceiverEnabled(userId, packageName);
 
                 if (!isContentCaptureReceiverEnabled) {
                     // Full package is not allowlisted: check individual components next
@@ -1284,13 +1303,13 @@
         @Override // from GlobalWhitelistState
         public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
             return isContentCaptureReceiverEnabled(userId, packageName)
-                    || isContentProtectionReceiverEnabled(packageName);
+                    || isContentProtectionReceiverEnabled(userId, packageName);
         }
 
         @Override // from GlobalWhitelistState
         public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
             return super.isWhitelisted(userId, componentName)
-                    || isContentProtectionReceiverEnabled(componentName.getPackageName());
+                    || isContentProtectionReceiverEnabled(userId, componentName.getPackageName());
         }
 
         private boolean isContentCaptureReceiverEnabled(
@@ -1298,9 +1317,11 @@
             return super.isWhitelisted(userId, packageName);
         }
 
-        private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
+        private boolean isContentProtectionReceiverEnabled(
+                @UserIdInt int userId, @NonNull String packageName) {
             if (mContentProtectionServiceComponentName == null
-                    || mContentProtectionBlocklistManager == null) {
+                    || mContentProtectionBlocklistManager == null
+                    || mContentProtectionConsentManager == null) {
                 return false;
             }
             synchronized (mLock) {
@@ -1308,7 +1329,8 @@
                     return false;
                 }
             }
-            return mContentProtectionBlocklistManager.isAllowed(packageName);
+            return mContentProtectionConsentManager.isConsentGranted(userId)
+                    && mContentProtectionBlocklistManager.isAllowed(packageName);
         }
     }
 
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
new file mode 100644
index 0000000..2eb758c
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
@@ -0,0 +1,109 @@
+/*
+ * 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.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Manages consent for content protection.
+ *
+ * @hide
+ */
+public class ContentProtectionConsentManager {
+
+    private static final String TAG = "ContentProtectionConsentManager";
+
+    private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
+
+    @NonNull private final ContentResolver mContentResolver;
+
+    @NonNull private final DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    public final ContentObserver mContentObserver;
+
+    private volatile boolean mCachedPackageVerifierConsent;
+
+    public ContentProtectionConsentManager(
+            @NonNull Handler handler,
+            @NonNull ContentResolver contentResolver,
+            @NonNull DevicePolicyManagerInternal devicePolicyManagerInternal) {
+        mContentResolver = contentResolver;
+        mDevicePolicyManagerInternal = devicePolicyManagerInternal;
+        mContentObserver = new SettingsObserver(handler);
+
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT),
+                /* notifyForDescendants= */ false,
+                mContentObserver,
+                UserHandle.USER_ALL);
+        mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+    }
+
+    /**
+     * Returns true if all the consents are granted
+     */
+    public boolean isConsentGranted(@UserIdInt int userId) {
+        return mCachedPackageVerifierConsent && !isUserOrganizationManaged(userId);
+    }
+
+    private boolean isPackageVerifierConsentGranted() {
+        // Not always cached internally
+        return Settings.Global.getInt(
+                        mContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, /* def= */ 0)
+                >= 1;
+    }
+
+    private boolean isUserOrganizationManaged(@UserIdInt int userId) {
+        // Cached internally
+        return mDevicePolicyManagerInternal.isUserOrganizationManaged(userId);
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
+            final String property = uri.getLastPathSegment();
+            if (property == null) {
+                return;
+            }
+            switch (property) {
+                case KEY_PACKAGE_VERIFIER_USER_CONSENT:
+                    mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+                    return;
+                default:
+                    Slog.w(TAG, "Ignoring unexpected property: " + property);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 5b11cfe..4bfc090 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -36,16 +36,18 @@
 
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
-            Runnable modeChangeCallback) {
-        this(hbmController, modeChangeCallback,
+            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) {
+        this(hbmController, modeChangeCallback, displayDeviceConfig,
                 new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
     }
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
-            Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) {
+            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+            DeviceConfigParameterProvider configParameterProvider) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
         mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
+        mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData());
     }
 
     void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index dd5afa2..da51569 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -33,12 +33,15 @@
     private final String mDisplayBrightnessStrategyName;
     private final boolean mShouldUseAutoBrightness;
 
+    private final boolean mIsSlowChange;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
         mBrightnessReason = builder.getBrightnessReason();
         mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
         mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
+        mIsSlowChange = builder.isSlowChange();
     }
 
     /**
@@ -77,6 +80,13 @@
         return mShouldUseAutoBrightness;
     }
 
+    /**
+     * @return {@code true} if the should transit to new state slowly
+     */
+    public boolean isSlowChange() {
+        return mIsSlowChange;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -88,6 +98,8 @@
         stringBuilder.append(getBrightnessReason());
         stringBuilder.append("\n    shouldUseAutoBrightness:");
         stringBuilder.append(getShouldUseAutoBrightness());
+        stringBuilder.append("\n    isSlowChange:");
+        stringBuilder.append(mIsSlowChange);
         return stringBuilder.toString();
     }
 
@@ -111,13 +123,14 @@
                 && mBrightnessReason.equals(otherState.getBrightnessReason())
                 && TextUtils.equals(mDisplayBrightnessStrategyName,
                         otherState.getDisplayBrightnessStrategyName())
-                && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness();
+                && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
+                && mIsSlowChange == otherState.isSlowChange();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(
-                mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness);
+        return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
+                mShouldUseAutoBrightness, mIsSlowChange);
     }
 
     /**
@@ -129,6 +142,7 @@
         private BrightnessReason mBrightnessReason = new BrightnessReason();
         private String mDisplayBrightnessStrategyName;
         private boolean mShouldUseAutoBrightness;
+        private boolean mIsSlowChange;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -143,6 +157,7 @@
             builder.setBrightnessReason(state.getBrightnessReason());
             builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName());
             builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
+            builder.setIsSlowChange(state.isSlowChange());
             return builder;
         }
 
@@ -237,6 +252,21 @@
         }
 
         /**
+         * See {@link DisplayBrightnessState#isSlowChange()}.
+         */
+        public Builder setIsSlowChange(boolean shouldUseAutoBrightness) {
+            this.mIsSlowChange = shouldUseAutoBrightness;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#isSlowChange()}.
+         */
+        public boolean isSlowChange() {
+            return mIsSlowChange;
+        }
+
+        /**
          * This is used to construct an immutable DisplayBrightnessState object from its builder
          */
         public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c5d0c17..46b543b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -673,7 +673,7 @@
         HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
 
         mBrightnessRangeController = new BrightnessRangeController(hbmController,
-                modeChangeCallback);
+                modeChangeCallback, mDisplayDeviceConfig);
 
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
@@ -2286,8 +2286,17 @@
             if (!reportOnly && mPowerState.getScreenState() != state
                     && readyToUpdateDisplayState()) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-                // TODO(b/153319140) remove when we can get this from the above trace invocation
-                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+
+                String propertyKey = "debug.tracing.screen_state";
+                String propertyValue = String.valueOf(state);
+                try {
+                    // TODO(b/153319140) remove when we can get this from the above trace invocation
+                    SystemProperties.set(propertyKey, propertyValue);
+                } catch (RuntimeException e) {
+                    Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                            + " value=" + propertyValue + " " + e.getMessage());
+                }
+
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 noteScreenState(state);
@@ -2380,8 +2389,17 @@
         }
         if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-            // TODO(b/153319140) remove when we can get this from the above trace invocation
-            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+
+            String propertyKey = "debug.tracing.screen_brightness";
+            String propertyValue = String.valueOf(target);
+            try {
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set(propertyKey, propertyValue);
+            } catch (RuntimeException e) {
+                Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                        + " value=" + propertyValue + " " + e.getMessage());
+            }
+
             noteScreenBrightness(target);
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index c2c0c0a..1d8b494 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -442,9 +442,6 @@
     @Nullable
     private BrightnessMappingStrategy mIdleModeBrightnessMapper;
 
-    // Indicates whether we should ramp slowly to the brightness value to follow.
-    private boolean mBrightnessToFollowSlowChange;
-
     private boolean mIsRbcActive;
 
     // Animators.
@@ -553,7 +550,7 @@
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
         mBrightnessRangeController = new BrightnessRangeController(hbmController,
-                modeChangeCallback);
+                modeChangeCallback, mDisplayDeviceConfig);
 
         mDisplayBrightnessController =
                 new DisplayBrightnessController(context, null,
@@ -1289,7 +1286,7 @@
         // actual state instead of the desired one.
         animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
-        boolean slowChange = false;
+
         final boolean userSetBrightnessChanged = mDisplayBrightnessController
                 .updateUserSetScreenBrightness();
 
@@ -1298,11 +1295,7 @@
         float brightnessState = displayBrightnessState.getBrightness();
         float rawBrightnessState = displayBrightnessState.getBrightness();
         mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
-
-        if (displayBrightnessState.getBrightnessReason().getReason()
-                == BrightnessReason.REASON_FOLLOWER) {
-            slowChange = mBrightnessToFollowSlowChange;
-        }
+        boolean slowChange = displayBrightnessState.isSlowChange();
 
         // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
         // doesn't yet have a valid lux value to use with auto-brightness.
@@ -1352,6 +1345,7 @@
                             .getRawAutomaticScreenBrightness();
                     brightnessState = clampScreenBrightness(brightnessState);
                     // slowly adapt to auto-brightness
+                    // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
                     slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
                             && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
                     brightnessAdjustmentFlags =
@@ -1953,8 +1947,17 @@
             if (!reportOnly && mPowerState.getScreenState() != state
                     && readyToUpdateDisplayState()) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-                // TODO(b/153319140) remove when we can get this from the above trace invocation
-                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+
+                String propertyKey = "debug.tracing.screen_state";
+                String propertyValue = String.valueOf(state);
+                try {
+                    // TODO(b/153319140) remove when we can get this from the above trace invocation
+                    SystemProperties.set(propertyKey, propertyValue);
+                } catch (RuntimeException e) {
+                    Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                            + " value=" + propertyValue + " " + e.getMessage());
+                }
+
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 noteScreenState(state);
@@ -2029,8 +2032,17 @@
         }
         if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-            // TODO(b/153319140) remove when we can get this from the above trace invocation
-            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+
+            String propertyKey = "debug.tracing.screen_brightness";
+            String propertyValue = String.valueOf(target);
+            try {
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set(propertyKey, propertyValue);
+            } catch (RuntimeException e) {
+                Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                        + " value=" + propertyValue + " " + e.getMessage());
+            }
+
             noteScreenBrightness(target);
         }
     }
@@ -2256,17 +2268,17 @@
             boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
         if (nits < 0) {
-            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
+            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
         } else {
             float brightness = mDisplayBrightnessController.convertToFloatScale(nits);
             if (BrightnessUtils.isValidBrightnessValue(brightness)) {
-                mDisplayBrightnessController.setBrightnessToFollow(brightness);
+                mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
             } else {
                 // The device does not support nits
-                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
+                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness,
+                        slowChange);
             }
         }
-        mBrightnessToFollowSlowChange = slowChange;
         sendUpdatePowerState();
     }
 
@@ -2406,7 +2418,6 @@
         pw.println("  mReportedToPolicy="
                 + reportedToPolicyToString(mReportedScreenStateToPolicy));
         pw.println("  mIsRbcActive=" + mIsRbcActive);
-        pw.println("  mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange);
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
         mAutomaticBrightnessStrategy.dump(ipw);
 
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
index 3fae224..8bf675c 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
@@ -54,6 +54,16 @@
     public static DisplayBrightnessState constructDisplayBrightnessState(
             int brightnessChangeReason, float brightness, float sdrBrightness,
             String displayBrightnessStrategyName) {
+        return constructDisplayBrightnessState(brightnessChangeReason, brightness, sdrBrightness,
+                displayBrightnessStrategyName, /* slowChange= */ false);
+    }
+
+    /**
+     * A utility to construct the DisplayBrightnessState
+     */
+    public static DisplayBrightnessState constructDisplayBrightnessState(
+            int brightnessChangeReason, float brightness, float sdrBrightness,
+            String displayBrightnessStrategyName, boolean slowChange) {
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(brightnessChangeReason);
         return new DisplayBrightnessState.Builder()
@@ -61,6 +71,7 @@
                 .setSdrBrightness(sdrBrightness)
                 .setBrightnessReason(brightnessReason)
                 .setDisplayBrightnessStrategyName(displayBrightnessStrategyName)
+                .setIsSlowChange(slowChange)
                 .build();
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index ffd62a3..d6f0098 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -164,10 +164,10 @@
     /**
      * Sets the brightness to follow
      */
-    public void setBrightnessToFollow(Float brightnessToFollow) {
+    public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) {
         synchronized (mLock) {
             mDisplayBrightnessStrategySelector.getFollowerDisplayBrightnessStrategy()
-                    .setBrightnessToFollow(brightnessToFollow);
+                    .setBrightnessToFollow(brightnessToFollow, slowChange);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 090ec13..585f576 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -37,9 +37,13 @@
     // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no brightness to follow set.
     private float mBrightnessToFollow;
 
+    // Indicates whether we should ramp slowly to the brightness value to follow.
+    private boolean mBrightnessToFollowSlowChange;
+
     public FollowerBrightnessStrategy(int displayId) {
         mDisplayId = displayId;
         mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mBrightnessToFollowSlowChange = false;
     }
 
     @Override
@@ -48,7 +52,7 @@
         // Todo(b/241308599): Introduce a validator class and add validations before setting
         // the brightness
         return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_FOLLOWER,
-                mBrightnessToFollow, mBrightnessToFollow, getName());
+                mBrightnessToFollow, mBrightnessToFollow, getName(), mBrightnessToFollowSlowChange);
     }
 
     @Override
@@ -60,8 +64,12 @@
         return mBrightnessToFollow;
     }
 
-    public void setBrightnessToFollow(float brightnessToFollow) {
+    /**
+     * Updates brightness value and brightness slowChange flag
+     **/
+    public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) {
         mBrightnessToFollow = brightnessToFollow;
+        mBrightnessToFollowSlowChange = slowChange;
     }
 
     /**
@@ -71,5 +79,6 @@
         writer.println("FollowerBrightnessStrategy:");
         writer.println("  mDisplayId=" + mDisplayId);
         writer.println("  mBrightnessToFollow:" + mBrightnessToFollow);
+        writer.println("  mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange);
     }
 }
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 633bf731..0e8a5fb 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -17,6 +17,8 @@
 package com.android.server.dreams;
 
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
+import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS;
 
 import android.app.ActivityTaskManager;
 import android.app.BroadcastOptions;
@@ -72,6 +74,7 @@
     private final Handler mHandler;
     private final Listener mListener;
     private final ActivityTaskManager mActivityTaskManager;
+    private final PowerManager mPowerManager;
 
     private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND);
@@ -84,6 +87,15 @@
     private final Intent mCloseNotificationShadeIntent;
     private final Bundle mCloseNotificationShadeOptions;
 
+    /**
+     * If this flag is on, we report user activity to {@link PowerManager} so that the screen
+     * doesn't shut off immediately when a dream quits unexpectedly. The device will instead go to
+     * keyguard and time out back to dreaming shortly.
+     *
+     * This allows the dream a second chance to relaunch in case of an app update or other crash.
+     */
+    private final boolean mResetScreenTimeoutOnUnexpectedDreamExit;
+
     private DreamRecord mCurrentDream;
 
     // Whether a dreaming started intent has been broadcast.
@@ -101,6 +113,7 @@
         mHandler = handler;
         mListener = listener;
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
         mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mCloseNotificationShadeIntent.putExtra(EXTRA_REASON_KEY, EXTRA_REASON_VALUE);
         mCloseNotificationShadeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -110,6 +123,8 @@
                         EXTRA_REASON_VALUE)
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                 .toBundle();
+        mResetScreenTimeoutOnUnexpectedDreamExit = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit);
     }
 
     /**
@@ -235,6 +250,17 @@
     }
 
     /**
+     * Sends a user activity signal to PowerManager to stop the screen from turning off immediately
+     * if there hasn't been any user interaction in a while.
+     */
+    private void resetScreenTimeout() {
+        Slog.i(TAG, "Resetting screen timeout");
+        long time = SystemClock.uptimeMillis();
+        mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER,
+                USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
+    }
+
+    /**
      * Stops dreaming.
      *
      * The current dream, if any, and any unstopped previous dreams are stopped. The device stops
@@ -448,6 +474,9 @@
             mHandler.post(() -> {
                 mService = null;
                 if (mCurrentDream == DreamRecord.this) {
+                    if (mResetScreenTimeoutOnUnexpectedDreamExit) {
+                        resetScreenTimeout();
+                    }
                     stopDream(true /*immediate*/, "binder died");
                 }
             });
@@ -473,6 +502,9 @@
             mHandler.post(() -> {
                 mService = null;
                 if (mCurrentDream == DreamRecord.this) {
+                    if (mResetScreenTimeoutOnUnexpectedDreamExit) {
+                        resetScreenTimeout();
+                    }
                     stopDream(true /*immediate*/, "service disconnected");
                 }
             });
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index a1b67e1..f1698dd 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -37,6 +37,7 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.GuardedBy;
@@ -75,7 +76,8 @@
     @GuardedBy("ImfLock.class")
     @Override
     public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
         if (curMethod != null) {
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index c53f1a5..b12a816 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -30,6 +30,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
 
@@ -198,8 +199,8 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags,
-            ResultReceiver resultReceiver) {
+    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+            @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         try {
             mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
index 27f6a89..29fa369 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
@@ -21,6 +21,7 @@
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethod;
 
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 
@@ -34,13 +35,13 @@
      *
      * @param showInputToken A token that represents the requester to show IME.
      * @param statsToken     A token that tracks the progress of an IME request.
-     * @param showFlags      Provides additional operating flags to show IME.
      * @param resultReceiver If non-null, this will be called back to the caller when
      *                       it has processed request to tell what it has done.
      * @param reason         The reason for requesting to show IME.
      */
     default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
+            @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {}
 
     /**
      * Performs hiding IME to the given window
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index f012d91..9ad4628 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -221,17 +221,21 @@
 
     /**
      * Called when {@link InputMethodManagerService} is processing the show IME request.
-     * @param statsToken The token for tracking this show request
-     * @param showFlags The additional operation flags to indicate whether this show request mode is
-     *                  implicit or explicit.
-     * @return {@code true} when the computer has proceed this show request operation.
+     *
+     * @param statsToken The token for tracking this show request.
+     * @return {@code true} when the show request can proceed.
      */
-    boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
+    boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.ShowFlags int showFlags) {
         if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
             ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
             return false;
         }
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+        // We only "set" the state corresponding to the flags, as this will be reset
+        // in clearImeShowFlags during a hide request.
+        // Thus, we keep the strongest values set (e.g. an implicit show right after
+        // an explicit show will still be considered explicit, likewise for forced).
         if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
             mRequestedShowExplicitly = true;
             mShowForced = true;
@@ -243,12 +247,12 @@
 
     /**
      * Called when {@link InputMethodManagerService} is processing the hide IME request.
-     * @param statsToken The token for tracking this hide request
-     * @param hideFlags The additional operation flags to indicate whether this hide request mode is
-     *                  implicit or explicit.
-     * @return {@code true} when the computer has proceed this hide request operations.
+     *
+     * @param statsToken The token for tracking this hide request.
+     * @return {@code true} when the hide request can proceed.
      */
-    boolean canHideIme(@NonNull ImeTracker.Token statsToken, int hideFlags) {
+    boolean canHideIme(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.HideFlags int hideFlags) {
         if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mRequestedShowExplicitly || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
@@ -264,13 +268,31 @@
         return true;
     }
 
-    int getImeShowFlags() {
+    /**
+     * Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags}
+     * to {@link InputMethod.ShowFlags}.
+     */
+    @InputMethod.ShowFlags
+    int getShowFlagsForInputMethodServiceOnly() {
         int flags = 0;
         if (mShowForced) {
             flags |= InputMethod.SHOW_FORCED | InputMethod.SHOW_EXPLICIT;
         } else if (mRequestedShowExplicitly) {
             flags |= InputMethod.SHOW_EXPLICIT;
-        } else {
+        }
+        return flags;
+    }
+
+    /**
+     * Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags}
+     * to {@link InputMethodManager.ShowFlags}.
+     */
+    @InputMethodManager.ShowFlags
+    int getShowFlags() {
+        int flags = 0;
+        if (mShowForced) {
+            flags |= InputMethodManager.SHOW_FORCED;
+        } else if (!mRequestedShowExplicitly) {
             flags |= InputMethodManager.SHOW_IMPLICIT;
         }
         return flags;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7bda2c1..c5fbcb9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2468,7 +2468,7 @@
             final ImeTracker.Token statsToken = mCurStatsToken;
             mCurStatsToken = null;
             showCurrentInputLocked(mCurFocusedWindow, statsToken,
-                    mVisibilityStateComputer.getImeShowFlags(),
+                    mVisibilityStateComputer.getShowFlags(),
                     null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
@@ -3404,8 +3404,9 @@
 
     @Override
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickTooType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
@@ -3578,15 +3579,17 @@
 
     @GuardedBy("ImfLock.class")
     boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethodManager.ShowFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         return showCurrentInputLocked(windowToken, statsToken, flags,
                 MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
     }
 
     @GuardedBy("ImfLock.class")
     private boolean showCurrentInputLocked(IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
             statsToken = createStatsTokenForFocusedClient(true /* show */,
@@ -3617,7 +3620,8 @@
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             mVisibilityApplier.performShowIme(windowToken, statsToken,
-                    mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason);
+                    mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
+                    resultReceiver, reason);
             mVisibilityStateComputer.setInputShown(true);
             return true;
         } else {
@@ -3629,8 +3633,8 @@
 
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput");
@@ -3660,7 +3664,8 @@
 
     @GuardedBy("ImfLock.class")
     boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
             statsToken = createStatsTokenForFocusedClient(false /* show */,
@@ -4847,7 +4852,7 @@
     }
 
     @BinderThread
-    private void hideMySoftInput(@NonNull IBinder token, int flags,
+    private void hideMySoftInput(@NonNull IBinder token, @InputMethodManager.HideFlags int flags,
             @SoftInputShowHideReason int reason) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
@@ -4869,7 +4874,7 @@
     }
 
     @BinderThread
-    private void showMySoftInput(@NonNull IBinder token, int flags) {
+    private void showMySoftInput(@NonNull IBinder token, @InputMethodManager.ShowFlags int flags) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
             synchronized (ImfLock.class) {
@@ -6828,8 +6833,8 @@
 
         @BinderThread
         @Override
-        public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason,
-                AndroidFuture future /* T=Void */) {
+        public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
+                @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
@@ -6842,7 +6847,8 @@
 
         @BinderThread
         @Override
-        public void showMySoftInput(int flags, AndroidFuture future /* T=Void */) {
+        public void showMySoftInput(@InputMethodManager.ShowFlags int flags,
+                AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6df3809..37879ef 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -118,7 +118,6 @@
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
@@ -1221,8 +1220,7 @@
                 }
             }
 
-            int mustNotHaveFlags = mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)
-                    ? FLAG_NO_DISMISS : FLAG_ONGOING_EVENT;
+            int mustNotHaveFlags = FLAG_NO_DISMISS;
             cancelNotification(callingUid, callingPid, pkg, tag, id,
                     /* mustHaveFlags= */ 0,
                     /* mustNotHaveFlags= */ mustNotHaveFlags,
@@ -2491,16 +2489,6 @@
         getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
                 ReviewNotificationPermissionsReceiver.getFilter(),
                 Context.RECEIVER_NOT_EXPORTED);
-
-        mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
-                new AppOpsManager.OnOpChangedInternalListener() {
-                    @Override
-                    public void onOpChanged(@NonNull String op, @NonNull String packageName,
-                            int userId) {
-                        mHandler.post(
-                                () -> handleNotificationPermissionChange(packageName, userId));
-                    }
-                });
     }
 
     /**
@@ -3560,16 +3548,20 @@
             }
             mPermissionHelper.setNotificationPermission(
                     pkg, UserHandle.getUserId(uid), enabled, true);
+            sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
 
             mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
                     .setType(MetricsEvent.TYPE_ACTION)
                     .setPackageName(pkg)
                     .setSubtype(enabled ? 1 : 0));
             mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
+            // Now, cancel any outstanding notifications that are part of a just-disabled app
+            if (!enabled) {
+                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0,
+                        UserHandle.getUserId(uid), REASON_PACKAGE_BANNED);
+            }
 
-            // Outstanding notifications from this package will be cancelled, and the package will
-            // be sent the ACTION_APP_BLOCK_STATE_CHANGED broadcast, as soon as we get the
-            // callback from AppOpsManager.
+            handleSavePolicyFile();
         }
 
         /**
@@ -5892,21 +5884,6 @@
         }
     };
 
-    private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
-        int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId);
-        if (uid == INVALID_UID) {
-            Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId));
-            return;
-        }
-        boolean hasPermission = mPermissionHelper.hasPermission(uid);
-        sendAppBlockStateChangedBroadcast(pkg, uid, !hasPermission);
-        if (!hasPermission) {
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null,
-                    /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId,
-                    REASON_PACKAGE_BANNED);
-        }
-    }
-
     protected void checkNotificationListenerAccess() {
         if (!isCallerSystemOrPhone()) {
             getContext().enforceCallingPermission(
@@ -6886,13 +6863,11 @@
         }
 
         // Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS
-        if (mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)) {
-            if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
-                    && canBeNonDismissible(ai, notification)) {
-                notification.flags |= FLAG_NO_DISMISS;
-            } else {
-                notification.flags &= ~FLAG_NO_DISMISS;
-            }
+        if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
+                && canBeNonDismissible(ai, notification)) {
+            notification.flags |= FLAG_NO_DISMISS;
+        } else {
+            notification.flags &= ~FLAG_NO_DISMISS;
         }
 
         int canColorize = getContext().checkPermission(
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 36a0b0c..1f5bd3e 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -75,6 +75,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.internal.logging.MetricsLogger;
@@ -108,30 +109,34 @@
     static final int RULE_LIMIT_PER_PACKAGE = 100;
 
     // pkg|userId => uid
-    protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
+    @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
 
     private final Context mContext;
     private final H mHandler;
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
-    @VisibleForTesting protected final NotificationManager mNotificationManager;
+    private final NotificationManager mNotificationManager;
     private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
-    @VisibleForTesting protected ZenModeConfig mDefaultConfig;
+    private ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final ZenModeFiltering mFiltering;
-    protected final RingerModeDelegate mRingerModeDelegate = new
+    private final RingerModeDelegate mRingerModeDelegate = new
             RingerModeDelegate();
     @VisibleForTesting protected final ZenModeConditions mConditions;
-    Object mConfigsLock = new Object();
+    private final Object mConfigsArrayLock = new Object();
+    @GuardedBy("mConfigsArrayLock")
     @VisibleForTesting final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
     private final Metrics mMetrics = new Metrics();
     private final ConditionProviders.Config mServiceConfig;
-    private SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
-    @VisibleForTesting protected ZenModeEventLogger mZenModeEventLogger;
+    private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
+    private final ZenModeEventLogger mZenModeEventLogger;
 
     @VisibleForTesting protected int mZenMode;
     @VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy;
     private int mUser = UserHandle.USER_SYSTEM;
+
+    private final Object mConfigLock = new Object();
+    @GuardedBy("mConfigLock")
     @VisibleForTesting protected ZenModeConfig mConfig;
     @VisibleForTesting protected AudioManagerInternal mAudioManager;
     protected PackageManager mPm;
@@ -159,7 +164,7 @@
         mDefaultConfig = readDefaultConfig(mContext.getResources());
         updateDefaultAutomaticRuleNames();
         mConfig = mDefaultConfig.copy();
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
         }
         mConsolidatedPolicy = mConfig.toNotificationPolicy();
@@ -186,7 +191,7 @@
     public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
             ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
             int callingUid) {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy,
                     userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity,
                     callingUid);
@@ -206,7 +211,7 @@
     }
 
     public boolean shouldIntercept(NotificationRecord record) {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             return mFiltering.shouldIntercept(mZenMode, mConsolidatedPolicy, record);
         }
     }
@@ -221,7 +226,7 @@
 
     public void initZenMode() {
         if (DEBUG) Log.d(TAG, "initZenMode");
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             // "update" config to itself, which will have no effect in the case where a config
             // was read in via XML, but will initialize zen mode if nothing was read in and the
             // config remains the default.
@@ -250,7 +255,7 @@
     public void onUserRemoved(int user) {
         if (user < UserHandle.USER_SYSTEM) return;
         if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user);
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             mConfigs.remove(user);
         }
     }
@@ -268,7 +273,7 @@
         mUser = user;
         if (DEBUG) Log.d(TAG, reason + " u=" + user);
         ZenModeConfig config = null;
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             if (mConfigs.get(user) != null) {
                 config = mConfigs.get(user).copy();
             }
@@ -278,7 +283,7 @@
             config = mDefaultConfig.copy();
             config.user = user;
         }
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
         }
         cleanUpZenRules();
@@ -314,7 +319,7 @@
 
     public List<ZenRule> getZenRules() {
         List<ZenRule> rules = new ArrayList<>();
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return rules;
             for (ZenRule rule : mConfig.automaticRules.values()) {
                 if (canManageAutomaticZenRule(rule)) {
@@ -327,7 +332,7 @@
 
     public AutomaticZenRule getAutomaticZenRule(String id) {
         ZenRule rule;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return null;
              rule = mConfig.automaticRules.get(id);
         }
@@ -364,7 +369,7 @@
         }
 
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) {
                 throw new AndroidRuntimeException("Could not create rule");
             }
@@ -387,7 +392,7 @@
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
             String reason, int callingUid, boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return false;
             if (DEBUG) {
                 Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
@@ -419,7 +424,7 @@
     public boolean removeAutomaticZenRule(String id, String reason, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return false;
             newConfig = mConfig.copy();
             ZenRule ruleToRemove = newConfig.automaticRules.get(id);
@@ -450,7 +455,7 @@
     public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return false;
             newConfig = mConfig.copy();
             for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
@@ -467,7 +472,7 @@
     public void setAutomaticZenRuleState(String id, Condition condition, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return;
 
             newConfig = mConfig.copy();
@@ -481,7 +486,7 @@
     public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return;
             newConfig = mConfig.copy();
 
@@ -491,6 +496,7 @@
         }
     }
 
+    @GuardedBy("mConfigLock")
     private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
             Condition condition, int callingUid, boolean fromSystemOrSystemUi) {
         if (rules == null || rules.isEmpty()) return;
@@ -538,7 +544,7 @@
             return 0;
         }
         int count = 0;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             for (ZenRule rule : mConfig.automaticRules.values()) {
                 if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) {
                     count++;
@@ -555,7 +561,7 @@
             return 0;
         }
         int count = 0;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             for (ZenRule rule : mConfig.automaticRules.values()) {
                 if (pkg.equals(rule.getPkg())) {
                     count++;
@@ -588,19 +594,23 @@
 
     protected void updateDefaultZenRules(int callingUid, boolean fromSystemOrSystemUi) {
         updateDefaultAutomaticRuleNames();
-        for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
-            ZenRule currRule = mConfig.automaticRules.get(defaultRule.id);
-            // if default rule wasn't user-modified nor enabled, use localized name
-            // instead of previous system name
-            if (currRule != null && !currRule.modified && !currRule.enabled
-                    && !defaultRule.name.equals(currRule.name)) {
-                if (canManageAutomaticZenRule(currRule)) {
-                    if (DEBUG) Slog.d(TAG, "Locale change - updating default zen rule name "
-                            + "from " + currRule.name + " to " + defaultRule.name);
-                    // update default rule (if locale changed, name of rule will change)
-                    currRule.name = defaultRule.name;
-                    updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule),
-                            "locale changed", callingUid, fromSystemOrSystemUi);
+        synchronized (mConfigLock) {
+            for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
+                ZenRule currRule = mConfig.automaticRules.get(defaultRule.id);
+                // if default rule wasn't user-modified nor enabled, use localized name
+                // instead of previous system name
+                if (currRule != null && !currRule.modified && !currRule.enabled
+                        && !defaultRule.name.equals(currRule.name)) {
+                    if (canManageAutomaticZenRule(currRule)) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Locale change - updating default zen rule name "
+                                    + "from " + currRule.name + " to " + defaultRule.name);
+                        }
+                        // update default rule (if locale changed, name of rule will change)
+                        currRule.name = defaultRule.name;
+                        updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule),
+                                "locale changed", callingUid, fromSystemOrSystemUi);
+                    }
                 }
             }
         }
@@ -686,7 +696,7 @@
     private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller,
             boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return;
             if (!Global.isValidZenMode(zenMode)) return;
             if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
@@ -715,7 +725,7 @@
 
     void dump(ProtoOutputStream proto) {
         proto.write(ZenModeProto.ZEN_MODE, mZenMode);
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig.manualRule != null) {
                 mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS);
             }
@@ -737,14 +747,14 @@
         pw.println(Global.zenModeToString(mZenMode));
         pw.print(prefix);
         pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString());
-        synchronized(mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             final int N = mConfigs.size();
             for (int i = 0; i < N; i++) {
                 dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
             }
         }
         pw.print(prefix); pw.print("mUser="); pw.println(mUser);
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             dump(pw, prefix, "mConfig", mConfig);
         }
 
@@ -833,7 +843,7 @@
                         Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
             }
             if (DEBUG) Log.d(TAG, reason);
-            synchronized (mConfig) {
+            synchronized (mConfigLock) {
                 setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
             }
         }
@@ -841,7 +851,7 @@
 
     public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId)
             throws IOException {
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             final int n = mConfigs.size();
             for (int i = 0; i < n; i++) {
                 if (forBackup && mConfigs.keyAt(i) != userId) {
@@ -856,7 +866,9 @@
      * @return user-specified default notification policy for priority only do not disturb
      */
     public Policy getNotificationPolicy() {
-        return getNotificationPolicy(mConfig);
+        synchronized (mConfigLock) {
+            return getNotificationPolicy(mConfig);
+        }
     }
 
     private static Policy getNotificationPolicy(ZenModeConfig config) {
@@ -867,8 +879,8 @@
      * Sets the global notification policy used for priority only do not disturb
      */
     public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) {
-        if (policy == null || mConfig == null) return;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
+            if (policy == null || mConfig == null) return;
             final ZenModeConfig newConfig = mConfig.copy();
             newConfig.applyNotificationPolicy(policy);
             setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid,
@@ -881,7 +893,7 @@
      */
     private void cleanUpZenRules() {
         long currentTime = System.currentTimeMillis();
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             final ZenModeConfig newConfig = mConfig.copy();
             if (newConfig.automaticRules != null) {
                 for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
@@ -906,7 +918,7 @@
      * @return a copy of the zen mode configuration
      */
     public ZenModeConfig getConfig() {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             return mConfig.copy();
         }
     }
@@ -918,7 +930,8 @@
         return mConsolidatedPolicy.copy();
     }
 
-    public boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
+    @GuardedBy("mConfigLock")
+    private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
             String reason, int callingUid, boolean fromSystemOrSystemUi) {
         return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/,
                 callingUid, fromSystemOrSystemUi);
@@ -926,11 +939,12 @@
 
     public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason,
             int callingUid, boolean fromSystemOrSystemUi) {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi);
         }
     }
 
+    @GuardedBy("mConfigLock")
     private boolean setConfigLocked(ZenModeConfig config, String reason,
             ComponentName triggeringComponent, boolean setRingerMode, int callingUid,
             boolean fromSystemOrSystemUi) {
@@ -942,7 +956,7 @@
             }
             if (config.user != mUser) {
                 // simply store away for background users
-                synchronized (mConfigsLock) {
+                synchronized (mConfigsArrayLock) {
                     mConfigs.put(config.user, config);
                 }
                 if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
@@ -951,7 +965,7 @@
             // handle CPS backed conditions - danger! may modify config
             mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
 
-            synchronized (mConfigsLock) {
+            synchronized (mConfigsArrayLock) {
                 mConfigs.put(config.user, config);
             }
             if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
@@ -979,6 +993,7 @@
      * Carries out a config update (if needed) and (re-)evaluates the zen mode value afterwards.
      * If logging is enabled, will also request logging of the outcome of this change if needed.
      */
+    @GuardedBy("mConfigLock")
     private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason,
             boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
         final boolean logZenModeEvents = mFlagResolver.isEnabled(
@@ -993,7 +1008,7 @@
         }
         final String val = Integer.toString(config.hashCode());
         Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
-        evaluateZenMode(reason, setRingerMode);
+        evaluateZenModeLocked(reason, setRingerMode);
         // After all changes have occurred, log if requested
         if (logZenModeEvents) {
             ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo(
@@ -1025,7 +1040,8 @@
     }
 
     @VisibleForTesting
-    protected void evaluateZenMode(String reason, boolean setRingerMode) {
+    @GuardedBy("mConfigLock")
+    protected void evaluateZenModeLocked(String reason, boolean setRingerMode) {
         if (DEBUG) Log.d(TAG, "evaluateZenMode");
         if (mConfig == null) return;
         final int policyHashBefore = mConsolidatedPolicy == null ? 0
@@ -1056,8 +1072,8 @@
     }
 
     private int computeZenMode() {
-        if (mConfig == null) return Global.ZEN_MODE_OFF;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
+            if (mConfig == null) return Global.ZEN_MODE_OFF;
             if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
             int zen = Global.ZEN_MODE_OFF;
             for (ZenRule automaticRule : mConfig.automaticRules.values()) {
@@ -1094,8 +1110,8 @@
     }
 
     private void updateConsolidatedPolicy(String reason) {
-        if (mConfig == null) return;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
+            if (mConfig == null) return;
             ZenPolicy policy = new ZenPolicy();
             if (mConfig.manualRule != null) {
                 applyCustomPolicy(policy, mConfig.manualRule);
@@ -1293,7 +1309,7 @@
      * Generate pulled atoms about do not disturb configurations.
      */
     public void pullRules(List<StatsEvent> events) {
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             final int numConfigs = mConfigs.size();
             for (int i = 0; i < numConfigs; i++) {
                 final int user = mConfigs.keyAt(i);
@@ -1319,6 +1335,7 @@
         }
     }
 
+    @GuardedBy("mConfigsArrayLock")
     private void ruleToProtoLocked(int user, ZenRule rule, boolean isManualRule,
             List<StatsEvent> events) {
         // Make the ID safe.
@@ -1389,7 +1406,7 @@
 
             if (mZenMode == Global.ZEN_MODE_OFF
                     || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                    && !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig))) {
+                    && !areAllPriorityOnlyRingerSoundsMuted())) {
                 // in priority only with ringer not muted, save ringer mode changes
                 // in dnd off, save ringer mode changes
                 setPreviousRingerModeSetting(ringerModeNew);
@@ -1410,8 +1427,7 @@
                             && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
                             || mZenMode == Global.ZEN_MODE_ALARMS
                             || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                            && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(
-                            mConfig)))) {
+                            && areAllPriorityOnlyRingerSoundsMuted()))) {
                         newZen = Global.ZEN_MODE_OFF;
                     } else if (mZenMode != Global.ZEN_MODE_OFF) {
                         ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
@@ -1430,6 +1446,12 @@
             return ringerModeExternalOut;
         }
 
+        private boolean areAllPriorityOnlyRingerSoundsMuted() {
+            synchronized (mConfigLock) {
+                return ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig);
+            }
+        }
+
         @Override
         public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
                 int ringerModeInternal, VolumePolicy policy) {
@@ -1633,7 +1655,7 @@
         private void emitRules() {
             final long now = SystemClock.elapsedRealtime();
             final long since = (now - mRuleCountLogTime);
-            synchronized (mConfig) {
+            synchronized (mConfigLock) {
                 int numZenRules = mConfig.automaticRules.size();
                 if (mNumZenRules != numZenRules
                         || since > MINIMUM_LOG_PERIOD_MS) {
@@ -1651,7 +1673,7 @@
         private void emitDndType() {
             final long now = SystemClock.elapsedRealtime();
             final long since = (now - mTypeLogTimeMs);
-            synchronized (mConfig) {
+            synchronized (mConfigLock) {
                 boolean dndOn = mZenMode != Global.ZEN_MODE_OFF;
                 int zenType = !dndOn ? DND_OFF
                         : (mConfig.manualRule != null) ? DND_ON_MANUAL : DND_ON_AUTOMATIC;
diff --git a/services/core/java/com/android/server/pm/DefaultAppProvider.java b/services/core/java/com/android/server/pm/DefaultAppProvider.java
index c18d0e9..fc61451 100644
--- a/services/core/java/com/android/server/pm/DefaultAppProvider.java
+++ b/services/core/java/com/android/server/pm/DefaultAppProvider.java
@@ -24,14 +24,10 @@
 import android.os.UserHandle;
 import android.util.Slog;
 
-import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.FgThread;
 
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -70,27 +66,19 @@
      * Set the package name of the default browser.
      *
      * @param packageName package name of the default browser, or {@code null} to unset
-     * @param async whether the operation should be asynchronous
      * @param userId the user ID
-     * @return whether the default browser was successfully set.
      */
-    public boolean setDefaultBrowser(@Nullable String packageName, boolean async,
-            @UserIdInt int userId) {
-        if (userId == UserHandle.USER_ALL) {
-            return false;
-        }
+    public void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
         final RoleManager roleManager = mRoleManagerSupplier.get();
         if (roleManager == null) {
-            return false;
+            return;
         }
         final UserHandle user = UserHandle.of(userId);
         final Executor executor = FgThread.getExecutor();
-        final AndroidFuture<Void> future = new AndroidFuture<>();
         final Consumer<Boolean> callback = successful -> {
-            if (successful) {
-                future.complete(null);
-            } else {
-                future.completeExceptionally(new RuntimeException());
+            if (!successful) {
+                Slog.e(PackageManagerService.TAG, "Failed to set default browser to "
+                        + packageName);
             }
         };
         final long identity = Binder.clearCallingIdentity();
@@ -102,19 +90,9 @@
                 roleManager.clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, user, executor,
                         callback);
             }
-            if (!async) {
-                try {
-                    future.get(5, TimeUnit.SECONDS);
-                } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                    Slog.e(PackageManagerService.TAG, "Exception while setting default browser: "
-                            + packageName, e);
-                    return false;
-                }
-            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2fc22bf..dbc2fd8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3535,6 +3535,18 @@
         // within these users.
         mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);
 
+        // Restore default browser setting if it is now installed.
+        String defaultBrowser;
+        synchronized (mLock) {
+            defaultBrowser = mSettings.getPendingDefaultBrowserLPr(userId);
+        }
+        if (Objects.equals(packageName, defaultBrowser)) {
+            mDefaultAppProvider.setDefaultBrowser(packageName, userId);
+            synchronized (mLock) {
+                mSettings.removePendingDefaultBrowserLPw(userId);
+            }
+        }
+
         // Persistent preferred activity might have came into effect due to this
         // install.
         mPreferredActivityHelper.updateDefaultHomeNotLocked(snapshotComputer(), userId);
@@ -6732,7 +6744,7 @@
         @Override
         public String removeLegacyDefaultBrowserPackageName(int userId) {
             synchronized (mLock) {
-                return mSettings.removeDefaultBrowserPackageNameLPw(userId);
+                return mSettings.removePendingDefaultBrowserLPw(userId);
             }
         }
 
@@ -7569,8 +7581,13 @@
                 callback);
     }
 
-    void setDefaultBrowser(@Nullable String packageName, boolean async, @UserIdInt int userId) {
-        mDefaultAppProvider.setDefaultBrowser(packageName, async, userId);
+    @Nullable
+    String getDefaultBrowser(@UserIdInt int userId) {
+        return mDefaultAppProvider.getDefaultBrowser(userId);
+    }
+
+    void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
+        mDefaultAppProvider.setDefaultBrowser(packageName, userId);
     }
 
     PackageUsage getPackageUsage() {
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 214a8b8..76e7070 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -557,9 +557,8 @@
             serializer.startDocument(null, true);
             serializer.startTag(null, TAG_DEFAULT_APPS);
 
-            synchronized (mPm.mLock) {
-                mPm.mSettings.writeDefaultAppsLPr(serializer, userId);
-            }
+            final String defaultBrowser = mPm.getDefaultBrowser(userId);
+            Settings.writeDefaultApps(serializer, defaultBrowser);
 
             serializer.endTag(null, TAG_DEFAULT_APPS);
             serializer.endDocument();
@@ -584,14 +583,19 @@
             parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
             restoreFromXml(parser, userId, TAG_DEFAULT_APPS,
                     (parser1, userId1) -> {
-                        final String defaultBrowser;
-                        synchronized (mPm.mLock) {
-                            mPm.mSettings.readDefaultAppsLPw(parser1, userId1);
-                            defaultBrowser = mPm.mSettings.removeDefaultBrowserPackageNameLPw(
-                                    userId1);
-                        }
+                        final String defaultBrowser = Settings.readDefaultApps(parser1);
                         if (defaultBrowser != null) {
-                            mPm.setDefaultBrowser(defaultBrowser, false, userId1);
+                            final PackageStateInternal packageState = mPm.snapshotComputer()
+                                    .getPackageStateInternal(defaultBrowser);
+                            if (packageState != null
+                                    && packageState.getUserStateOrDefault(userId1).isInstalled()) {
+                                mPm.setDefaultBrowser(defaultBrowser, userId1);
+                            } else {
+                                synchronized (mPm.mLock) {
+                                    mPm.mSettings.setPendingDefaultBrowserLPw(defaultBrowser,
+                                            userId1);
+                                }
+                            }
                         }
                     });
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 532ae71..677a5d1 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -517,9 +517,11 @@
     private final WatchedArrayMap<String, String> mRenamedPackages =
             new WatchedArrayMap<String, String>();
 
-    // For every user, it is used to find the package name of the default Browser App.
+    // For every user, it is used to find the package name of the default browser app pending to be
+    // applied, either on first boot after upgrade, or after backup & restore but before app is
+    // installed.
     @Watched
-    final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
+    final WatchedSparseArray<String> mPendingDefaultBrowser = new WatchedSparseArray<>();
 
     // TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
     //  verify.
@@ -592,7 +594,7 @@
         mAppIds.registerObserver(mObserver);
         mRenamedPackages.registerObserver(mObserver);
         mNextAppLinkGeneration.registerObserver(mObserver);
-        mDefaultBrowserApp.registerObserver(mObserver);
+        mPendingDefaultBrowser.registerObserver(mObserver);
         mPendingPackages.registerObserver(mObserver);
         mPastSignatures.registerObserver(mObserver);
         mKeySetRefs.registerObserver(mObserver);
@@ -787,7 +789,7 @@
 
         mRenamedPackages.snapshot(r.mRenamedPackages);
         mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
-        mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
+        mPendingDefaultBrowser.snapshot(r.mPendingDefaultBrowser);
         // mReadMessages
         mPendingPackages = r.mPendingPackagesSnapshot.snapshot();
         mPendingPackagesSnapshot = new SnapshotCache.Sealed<>();
@@ -1504,8 +1506,16 @@
         return cpir;
     }
 
-    String removeDefaultBrowserPackageNameLPw(int userId) {
-        return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
+    String getPendingDefaultBrowserLPr(int userId) {
+        return mPendingDefaultBrowser.get(userId);
+    }
+
+    void setPendingDefaultBrowserLPw(String defaultBrowser, int userId) {
+        mPendingDefaultBrowser.put(userId, defaultBrowser);
+    }
+
+    String removePendingDefaultBrowserLPw(int userId) {
+        return mPendingDefaultBrowser.removeReturnOld(userId);
     }
 
     private File getUserSystemDirectory(int userId) {
@@ -1688,6 +1698,19 @@
 
     void readDefaultAppsLPw(XmlPullParser parser, int userId)
             throws XmlPullParserException, IOException {
+        String defaultBrowser = readDefaultApps(parser);
+        if (defaultBrowser != null) {
+            mPendingDefaultBrowser.put(userId, defaultBrowser);
+        }
+    }
+
+    /**
+     * @return the package name for the default browser app, or {@code null} if none.
+     */
+    @Nullable
+    static String readDefaultApps(@NonNull XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String defaultBrowser = null;
         int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1697,8 +1720,7 @@
             }
             String tagName = parser.getName();
             if (tagName.equals(TAG_DEFAULT_BROWSER)) {
-                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
-                mDefaultBrowserApp.put(userId, packageName);
+                defaultBrowser = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
             } else if (tagName.equals(TAG_DEFAULT_DIALER)) {
                 // Ignored.
             } else {
@@ -1708,6 +1730,7 @@
                 XmlUtils.skipCurrentTag(parser);
             }
         }
+        return defaultBrowser;
     }
 
     void readBlockUninstallPackagesLPw(TypedXmlPullParser parser, int userId)
@@ -2087,8 +2110,13 @@
 
     void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
             throws IllegalArgumentException, IllegalStateException, IOException {
+        String defaultBrowser = mPendingDefaultBrowser.get(userId);
+        writeDefaultApps(serializer, defaultBrowser);
+    }
+
+    static void writeDefaultApps(@NonNull XmlSerializer serializer, @Nullable String defaultBrowser)
+            throws IllegalArgumentException, IllegalStateException, IOException {
         serializer.startTag(null, TAG_DEFAULT_APPS);
-        String defaultBrowser = mDefaultBrowserApp.get(userId);
         if (!TextUtils.isEmpty(defaultBrowser)) {
             serializer.startTag(null, TAG_DEFAULT_BROWSER);
             serializer.attribute(null, ATTR_PACKAGE_NAME, defaultBrowser);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index c5f63ce..a6d5c19 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -285,9 +285,9 @@
         final LaunchingState mLaunchingState;
 
         /** The type can be cold (new process), warm (new activity), or hot (bring to front). */
-        final int mTransitionType;
+        int mTransitionType;
         /** Whether the process was already running when the transition started. */
-        final boolean mProcessRunning;
+        boolean mProcessRunning;
         /** whether the process of the launching activity didn't have any active activity. */
         final boolean mProcessSwitch;
         /** The process state of the launching activity prior to the launch */
@@ -972,6 +972,19 @@
             // App isn't attached to record yet, so match with info.
             if (info.mLastLaunchedActivity.info.applicationInfo == appInfo) {
                 info.mBindApplicationDelayMs = info.calculateCurrentDelay();
+                if (info.mProcessRunning) {
+                    // It was HOT/WARM launch, but the process was died somehow right after the
+                    // launch request.
+                    info.mProcessRunning = false;
+                    info.mTransitionType = TYPE_TRANSITION_COLD_LAUNCH;
+                    final String msg = "Process " + info.mLastLaunchedActivity.info.processName
+                            + " restarted";
+                    Slog.i(TAG, msg);
+                    if (info.mLaunchingState.mTraceName != null) {
+                        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg + "#"
+                                + LaunchingState.sTraceSeqId);
+                    }
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index dd6bcb1..e93f358 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4516,7 +4516,7 @@
                     mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
                 }
                 // Post cleanup after the visibility and animation are transferred.
-                fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+                fromActivity.postWindowRemoveStartingWindowCleanup();
                 fromActivity.mVisibleSetFromTransferredStartingWindow = false;
 
                 mWmService.updateFocusedWindowLocked(
@@ -7443,27 +7443,12 @@
         }
     }
 
-    void postWindowRemoveStartingWindowCleanup(WindowState win) {
-        // TODO: Something smells about the code below...Is there a better way?
-        if (mStartingWindow == win) {
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Notify removed startingWindow %s", win);
-            removeStartingWindow();
-        } else if (mChildren.size() == 0) {
-            // If this is the last window and we had requested a starting transition window,
-            // well there is no point now.
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Nulling last startingData");
-            mStartingData = null;
-            if (mVisibleSetFromTransferredStartingWindow) {
-                // We set the visible state to true for the token from a transferred starting
-                // window. We now reset it back to false since the starting window was the last
-                // window in the token.
-                setVisible(false);
-            }
-        } else if (mChildren.size() == 1 && mStartingSurface != null && !isRelaunching()) {
-            // If this is the last window except for a starting transition window,
-            // we need to get rid of the starting transition.
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Last window, removing starting window %s", win);
-            removeStartingWindow();
+    void postWindowRemoveStartingWindowCleanup() {
+        if (mChildren.size() == 0 && mVisibleSetFromTransferredStartingWindow) {
+            // We set the visible state to true for the token from a transferred starting
+            // window. We now reset it back to false since the starting window was the last
+            // window in the token.
+            setVisible(false);
         }
     }
 
@@ -7994,6 +7979,9 @@
                 mLastReportedConfiguration.getMergedConfiguration())) {
             ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
                     false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
+            if (mTransitionController.inPlayingTransition(this)) {
+                mTransitionController.mValidateActivityCompat.add(this);
+            }
         }
 
         mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
@@ -9413,6 +9401,9 @@
         if (info.applicationInfo == null) {
             return info.getMinAspectRatio();
         }
+        if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
+            return mLetterboxUiController.getUserMinAspectRatio();
+        }
         if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
             return info.getMinAspectRatio();
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 50948e1..5553600 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2201,7 +2201,8 @@
             displaySwapping |= s.isDisplaySleepingAndSwapping();
             ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
                     + "finishing=%s", s, s.nowVisible, animating, s.finishing);
-            if ((!animating && !displaySwapping) || mService.mShuttingDown) {
+            if ((!animating && !displaySwapping) || mService.mShuttingDown
+                    || s.getRootTask().isForceHiddenForPinnedTask()) {
                 if (!processPausingActivities && s.isState(PAUSING)) {
                     // Defer processing pausing activities in this iteration and reschedule
                     // a delayed idle to reprocess it again
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cb7414e..2309e58 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2751,6 +2751,10 @@
     void onAppTransitionDone() {
         super.onAppTransitionDone();
         mWmService.mWindowsChanged = true;
+        onTransitionFinished();
+    }
+
+    void onTransitionFinished() {
         // If the transition finished callback cannot match the token for some reason, make sure the
         // rotated state is cleared if it is already invisible.
         if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 2b8312c..5f3d517 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -81,6 +81,7 @@
     private boolean mIsLeashReadyForDispatching;
     private final Rect mSourceFrame = new Rect();
     private final Rect mLastSourceFrame = new Rect();
+    private final Rect mLastContainerBounds = new Rect();
     private @NonNull Insets mInsetsHint = Insets.NONE;
     private @Flags int mFlagsFromFrameProvider;
     private @Flags int mFlagsFromServer;
@@ -278,11 +279,31 @@
         // visible. (i.e. No surface, pending insets that were given during layout, etc..)
         if (mServerVisible) {
             mSource.setFrame(mSourceFrame);
+            updateInsetsHint();
         } else {
             mSource.setFrame(0, 0, 0, 0);
         }
     }
 
+    // To be called when mSourceFrame or the window container bounds is changed.
+    private void updateInsetsHint() {
+        if (!mControllable || !mServerVisible) {
+            return;
+        }
+        final Rect bounds = mWindowContainer.getBounds();
+        if (mSourceFrame.equals(mLastSourceFrame) && bounds.equals(mLastContainerBounds)) {
+            return;
+        }
+        mLastSourceFrame.set(mSourceFrame);
+        mLastContainerBounds.set(bounds);
+        mInsetsHint = mSource.calculateInsets(bounds, true /* ignoreVisibility */);
+    }
+
+    @VisibleForTesting
+    Insets getInsetsHint() {
+        return mInsetsHint;
+    }
+
     /** @return A new source computed by the specified window frame in the given display frames. */
     InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
         final InsetsSource source = new InsetsSource(mSource);
@@ -338,15 +359,9 @@
                     mSetLeashPositionConsumer.accept(t);
                 }
             }
-            if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
-                final Insets insetsHint = mSource.calculateInsets(
-                        mWindowContainer.getBounds(), true /* ignoreVisibility */);
-                if (!insetsHint.equals(mControl.getInsetsHint())) {
-                    changed = true;
-                    mControl.setInsetsHint(insetsHint);
-                    mInsetsHint = insetsHint;
-                }
-                mLastSourceFrame.set(mSource.getFrame());
+            if (!mControl.getInsetsHint().equals(mInsetsHint)) {
+                mControl.setInsetsHint(mInsetsHint);
+                changed = true;
             }
             if (changed) {
                 mStateController.notifyControlChanged(mControlTarget);
@@ -587,6 +602,11 @@
             pw.print(prefix + "mControl=");
             mControl.dump("", pw);
         }
+        if (mControllable) {
+            pw.print(prefix + "mInsetsHint=");
+            pw.print(mInsetsHint);
+            pw.println();
+        }
         pw.print(prefix);
         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
         pw.println();
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 39f7570..394105a 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -40,6 +40,12 @@
 import static android.content.pm.ActivityInfo.isFixedOrientation;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.screenOrientationToString;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -103,6 +109,7 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.RemoteException;
 import android.util.Slog;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -237,6 +244,10 @@
     // Counter for ActivityRecord#setRequestedOrientation
     private int mSetOrientationRequestCounter = 0;
 
+    // The min aspect ratio override set by user
+    @PackageManager.UserMinAspectRatio
+    private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
+
     // The CompatDisplayInsets of the opaque activity beneath the translucent one.
     private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
 
@@ -1059,7 +1070,7 @@
 
     private float getDefaultMinAspectRatioForUnresizableApps() {
         if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
-                || mActivityRecord.getDisplayContent() == null) {
+                || mActivityRecord.getDisplayArea() == null) {
             return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
                     > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
                             ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
@@ -1071,8 +1082,8 @@
 
     float getSplitScreenAspectRatio() {
         // Getting the same aspect ratio that apps get in split screen.
-        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
-        if (displayContent == null) {
+        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+        if (displayArea == null) {
             return getDefaultMinAspectRatioForUnresizableApps();
         }
         int dividerWindowWidth =
@@ -1080,7 +1091,7 @@
         int dividerInsets =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
         int dividerSize = dividerWindowWidth - dividerInsets * 2;
-        final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds());
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
         if (bounds.width() >= bounds.height()) {
             bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
             bounds.right = bounds.centerX();
@@ -1091,14 +1102,57 @@
         return computeAspectRatio(bounds);
     }
 
+    boolean shouldApplyUserMinAspectRatioOverride() {
+        if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()) {
+            return false;
+        }
+
+        try {
+            final int userAspectRatio = mActivityRecord.mAtmService.getPackageManager()
+                    .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
+            mUserAspectRatio = userAspectRatio;
+            return userAspectRatio != USER_MIN_ASPECT_RATIO_UNSET;
+        } catch (RemoteException e) {
+            // Don't apply user aspect ratio override
+            Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
+            return false;
+        }
+    }
+
+    float getUserMinAspectRatio() {
+        switch (mUserAspectRatio) {
+            case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+                return getDisplaySizeMinAspectRatio();
+            case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+                return getSplitScreenAspectRatio();
+            case USER_MIN_ASPECT_RATIO_16_9:
+                return 16 / 9f;
+            case USER_MIN_ASPECT_RATIO_4_3:
+                return 4 / 3f;
+            case USER_MIN_ASPECT_RATIO_3_2:
+                return 3 / 2f;
+            default:
+                throw new AssertionError("Unexpected user min aspect ratio override: "
+                        + mUserAspectRatio);
+        }
+    }
+
+    private float getDisplaySizeMinAspectRatio() {
+        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+        if (displayArea == null) {
+            return mActivityRecord.info.getMinAspectRatio();
+        }
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+        return computeAspectRatio(bounds);
+    }
+
     private float getDefaultMinAspectRatio() {
-        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
-        if (displayContent == null
+        if (mActivityRecord.getDisplayArea() == null
                 || !mLetterboxConfiguration
                     .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
             return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
         }
-        return computeAspectRatio(new Rect(displayContent.getBounds()));
+        return getDisplaySizeMinAspectRatio();
     }
 
     Resources getResources() {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9ef5ed0..4faaf51 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -682,6 +682,26 @@
         }
     }
 
+    /**
+     * Removes the oldest recent task that is compatible with the given one. This is possible if
+     * the task windowing mode changed after being added to the Recents.
+     */
+    void removeCompatibleRecentTask(Task task) {
+        final int taskIndex = mTasks.indexOf(task);
+        if (taskIndex < 0) {
+            return;
+        }
+
+        final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */);
+        if (candidateIndex == -1) {
+            // Nothing to trim
+            return;
+        }
+
+        final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex);
+        remove(taskToRemove);
+    }
+
     void removeTasksByPackageName(String packageName, int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final Task task = mTasks.get(i);
@@ -1540,6 +1560,10 @@
      * list (if any).
      */
     private int findRemoveIndexForAddTask(Task task) {
+        return findRemoveIndexForTask(task, true /* includingSelf */);
+    }
+
+    private int findRemoveIndexForTask(Task task, boolean includingSelf) {
         final int recentsCount = mTasks.size();
         final Intent intent = task.intent;
         final boolean document = intent != null && intent.isDocument();
@@ -1595,6 +1619,8 @@
                     // existing task
                     continue;
                 }
+            } else if (!includingSelf) {
+                continue;
             }
             return i;
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c23beb..c5e5b1d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4504,6 +4504,10 @@
         return mForceHiddenFlags != 0;
     }
 
+    boolean isForceHiddenForPinnedTask() {
+        return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;
+    }
+
     @Override
     protected boolean isForceTranslucent() {
         return mForceTranslucent;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 0c1f33c..92c0987 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1020,7 +1020,7 @@
         final WindowContainer<?> parent = getParent();
         final Task thisTask = asTask();
         if (thisTask != null && parent.asTask() == null
-                && mTransitionController.isTransientHide(thisTask)) {
+                && mTransitionController.isTransientVisible(thisTask)) {
             // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule.
             return TASK_FRAGMENT_VISIBILITY_VISIBLE;
         }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 71192cd..b7c8092 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -407,6 +407,36 @@
         return false;
     }
 
+    /** Returns {@code true} if the task should keep visible if this is a transient transition. */
+    boolean isTransientVisible(@NonNull Task task) {
+        if (mTransientLaunches == null) return false;
+        int occludedCount = 0;
+        final int numTransient = mTransientLaunches.size();
+        for (int i = numTransient - 1; i >= 0; --i) {
+            final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask();
+            if (transientRoot == null) continue;
+            final WindowContainer<?> rootParent = transientRoot.getParent();
+            if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
+            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor
+                    .mOpaqueActivityHelper.getOpaqueActivity(rootParent);
+            if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
+                occludedCount++;
+            }
+        }
+        if (occludedCount == numTransient) {
+            for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
+                if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
+                    // Keep transient activity visible until transition finished, so it won't pause
+                    // with transient-hide tasks that may delay resuming the next top.
+                    return true;
+                }
+            }
+            // Let transient-hide activities pause before transition is finished.
+            return false;
+        }
+        return isInTransientHide(task);
+    }
+
     boolean canApplyDim(@NonNull Task task) {
         if (mTransientLaunches == null) return true;
         final Dimmer dimmer = task.getDimmer();
@@ -1298,6 +1328,7 @@
             if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
                 asyncRotationController.onTransitionFinished();
             }
+            dc.onTransitionFinished();
             if (hasParticipatedDisplay && dc.mDisplayRotationCompatPolicy != null) {
                 final ChangeInfo changeInfo = mChanges.get(dc);
                 if (changeInfo != null
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 79cb61b..37985ea 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
 import android.app.WindowConfiguration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
@@ -141,6 +142,14 @@
     final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>();
 
     /**
+     * List of activity-level participants. ActivityRecord is not expected to change independently,
+     * however, recent compatibility logic can now cause this at arbitrary times determined by
+     * client code. If it happens during an animation, the surface can be left at the wrong spot.
+     * TODO(b/290237710) remove when compat logic is moved.
+     */
+    final ArrayList<ActivityRecord> mValidateActivityCompat = new ArrayList<>();
+
+    /**
      * Currently playing transitions (in the order they were started). When finished, records are
      * removed from this list.
      */
@@ -468,15 +477,22 @@
         if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
         }
-        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
-            if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
-        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
         }
         return false;
     }
 
+    boolean isTransientVisible(@NonNull Task task) {
+        if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) {
+            return true;
+        }
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            if (mPlayingTransitions.get(i).isTransientVisible(task)) return true;
+        }
+        return false;
+    }
+
     boolean canApplyDim(@Nullable Task task) {
         if (task == null) {
             // Always allow non-activity window.
@@ -896,6 +912,14 @@
             }
         }
         mValidateCommitVis.clear();
+        for (int i = 0; i < mValidateActivityCompat.size(); ++i) {
+            ActivityRecord ar = mValidateActivityCompat.get(i);
+            if (ar.getSurfaceControl() == null) continue;
+            final Point tmpPos = new Point();
+            ar.getRelativePosition(tmpPos);
+            ar.getSyncTransaction().setPosition(ar.getSurfaceControl(), tmpPos.x, tmpPos.y);
+        }
+        mValidateActivityCompat.clear();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0e19671..3a19a3b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1996,7 +1996,7 @@
         }
 
         if (win.mActivityRecord != null) {
-            win.mActivityRecord.postWindowRemoveStartingWindowCleanup(win);
+            win.mActivityRecord.postWindowRemoveStartingWindowCleanup();
         }
 
         if (win.mAttrs.type == TYPE_WALLPAPER) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 541fa94..5d239eb 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1615,6 +1615,7 @@
         final int count = tasksToReparent.size();
         for (int i = 0; i < count; ++i) {
             final Task task = tasksToReparent.get(i);
+            final int prevWindowingMode = task.getWindowingMode();
             if (syncId >= 0) {
                 addToSyncSet(syncId, task);
             }
@@ -1628,6 +1629,12 @@
                         hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                         false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
             }
+            // Trim the compatible Recent task (if any) after the Task is reparented and now has
+            // a different windowing mode, in order to prevent redundant Recent tasks after
+            // reparenting.
+            if (prevWindowingMode != task.getWindowingMode()) {
+                mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task);
+            }
         }
 
         if (transition != null) transition.collect(newParent);
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
index 212ec14..bef56ce 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.frameworks.inputmethodtests">
 
-    <uses-sdk android:targetSdkVersion="31" />
     <queries>
         <intent>
             <action android:name="android.view.InputMethod" />
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
index 77e32a7..cedbfd2b 100644
--- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -9,5 +9,16 @@
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksImeTests",
+      "options": [
+        {"include-filter": "com.android.inputmethodservice"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
   ]
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
index 0104f71..b7de749 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.inputmethod.imetests">
 
-    <uses-sdk android:targetSdkVersion="31" />
-
     <!-- Permissions required for granting and logging -->
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 898658e..e8acb06 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -20,6 +20,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
+
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -45,6 +47,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -56,9 +59,9 @@
     private static final String EDIT_TEXT_DESC = "Input box";
     private static final long TIMEOUT_IN_SECONDS = 3;
     private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
-            "settings put secure show_ime_with_hard_keyboard 1";
+            "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1";
     private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
-            "settings put secure show_ime_with_hard_keyboard 0";
+            "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
 
     private Instrumentation mInstrumentation;
     private UiDevice mUiDevice;
@@ -82,29 +85,19 @@
         mUiDevice.freezeRotation();
         mUiDevice.setOrientationNatural();
         // Waits for input binding ready.
-        eventually(
-                () -> {
-                    mInputMethodService =
-                            InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
-                    assertThat(mInputMethodService).isNotNull();
+        eventually(() -> {
+            mInputMethodService =
+                    InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
+            assertThat(mInputMethodService).isNotNull();
 
-                    // The editor won't bring up keyboard by default.
-                    assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
-                    assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
-                });
-        // Save the original value of show_ime_with_hard_keyboard in Settings.
+            // The editor won't bring up keyboard by default.
+            assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+            assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
+        });
+        // Save the original value of show_ime_with_hard_keyboard from Settings.
         mShowImeWithHardKeyboardEnabled = Settings.Secure.getInt(
                 mInputMethodService.getContentResolver(),
                 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0;
-        // Disable showing Ime with hard keyboard because it is the precondition the for most test
-        // cases
-        if (mShowImeWithHardKeyboardEnabled) {
-            executeShellCommand(DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
-        }
-        mInputMethodService.getResources().getConfiguration().keyboard =
-                Configuration.KEYBOARD_NOKEYS;
-        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
-                Configuration.HARDKEYBOARDHIDDEN_YES;
     }
 
     @After
@@ -112,82 +105,141 @@
         mUiDevice.unfreezeRotation();
         executeShellCommand("ime disable " + mInputMethodId);
         // Change back the original value of show_ime_with_hard_keyboard in Settings.
-        executeShellCommand(mShowImeWithHardKeyboardEnabled ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
+        executeShellCommand(mShowImeWithHardKeyboardEnabled
+                ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
                 : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
     }
 
+    /**
+     * This checks that the IME can be shown and hidden by user actions
+     * (i.e. tapping on an EditText, tapping the Home button).
+     */
     @Test
-    public void testShowHideKeyboard_byUserAction() throws InterruptedException {
+    public void testShowHideKeyboard_byUserAction() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // Performs click on editor box to bring up the soft keyboard.
         Log.i(TAG, "Click on EditText.");
-        verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */);
-
-        // Press back key to hide soft keyboard.
-        Log.i(TAG, "Press back");
         verifyInputViewStatus(
-                () -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */);
+                () -> clickOnEditorText(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Press home key to hide soft keyboard.
+        Log.i(TAG, "Press home");
+        verifyInputViewStatus(
+                () -> assertThat(mUiDevice.pressHome()).isTrue(),
+                true /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that the IME can be shown and hidden using the WindowInsetsController APIs.
+     */
     @Test
-    public void testShowHideKeyboard_byApi() throws InterruptedException {
+    public void testShowHideKeyboard_byApi() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // Triggers to show IME via public API.
         verifyInputViewStatus(
                 () -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
         // Triggers to hide IME via public API.
         verifyInputViewStatusOnMainSync(
-                () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                () -> assertThat(mActivity.hideImeWithWindowInsetsController()).isTrue(),
+                true /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
+     */
     @Test
-    public void testShowHideSelf() throws InterruptedException {
-        // IME requests to show itself without any flags: expect shown.
+    public void testShowHideSelf() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // IME request to show itself without any flags, expect shown.
         Log.i(TAG, "Call IMS#requestShowSelf(0)");
         verifyInputViewStatusOnMainSync(
-                () -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */);
+                () -> mInputMethodService.requestShowSelf(0 /* flags */),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME requests to hide itself with flag: HIDE_IMPLICIT_ONLY, expect not hide (shown).
+        // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown).
         Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                false /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME request to hide itself without any flags: expect hidden.
+        // IME request to hide itself without any flags, expect hidden.
         Log.i(TAG, "Call IMS#requestHideSelf(0)");
         verifyInputViewStatusOnMainSync(
-                () -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */);
+                () -> mInputMethodService.requestHideSelf(0 /* flags */),
+                true /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
 
-        // IME request to show itself with flag SHOW_IMPLICIT: expect shown.
+        // IME request to show itself with flag SHOW_IMPLICIT, expect shown.
         Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME request to hide itself with flag: HIDE_IMPLICIT_ONLY, expect hidden.
+        // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden.
         Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                true /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks the return value of IMS#onEvaluateInputViewShown,
+     * when show_ime_with_hard_keyboard is enabled.
+     */
     @Test
     public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() throws Exception {
-        executeShellCommand(ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
-        mInstrumentation.waitForIdleSync();
+        setShowImeWithHardKeyboard(true /* enabled */);
 
-        // Simulate connecting a hard keyboard
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
                 Configuration.HARDKEYBOARDHIDDEN_NO;
+        eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
 
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_NO;
+        eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
     }
 
+    /**
+     * This checks the return value of IMSonEvaluateInputViewShown,
+     * when show_ime_with_hard_keyboard is disabled.
+     */
     @Test
-    public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() {
+    public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
@@ -196,6 +248,8 @@
 
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_NO;
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
 
         mInputMethodService.getResources().getConfiguration().keyboard =
@@ -205,149 +259,386 @@
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
     }
 
+    /**
+     * This checks that any (implicit or explicit) show request,
+     * when IMS#onEvaluateInputViewShown returns false, results in the IME not being shown.
+     */
     @Test
     public void testShowSoftInput_disableShowImeWithHardKeyboard() throws Exception {
-        // Simulate connecting a hard keyboard
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
                 Configuration.HARDKEYBOARDHIDDEN_NO;
+
         // When InputMethodService#onEvaluateInputViewShown() returns false, the Ime should not be
         // shown no matter what the show flag is.
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that an explicit show request results in the IME being shown.
+     */
     @Test
     public void testShowSoftInputExplicitly() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the
         // Ime should be shown.
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an implicit show request results in the IME being shown.
+     */
     @Test
     public void testShowSoftInputImplicitly() throws Exception {
-        // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT, the
-        // Ime should be shown.
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT,
+        // the IME should be shown.
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an explicit show request when the IME is not previously shown,
+     * and it should be shown in fullscreen mode, results in the IME being shown.
+     */
     @Test
-    public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
-        // When keyboard is off, InputMethodService#onEvaluateInputViewShown returns true, flag is
-        // IMPLICIT and InputMethodService#onEvaluateFullScreenMode returns true, the Ime should not
-        // be shown.
+    public void testShowSoftInputExplicitly_fullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Set orientation landscape to enable fullscreen mode.
         setOrientation(2);
         eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse());
-        // Wait for the TestActivity to be recreated
+        // Wait for the TestActivity to be recreated.
         eventually(() ->
                 assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
-        // Get the new TestActivity
+        // Get the new TestActivity.
         mActivity = TestActivity.getLastCreatedInstance();
         assertThat(mActivity).isNotNull();
         InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-        // Wait for the new EditText to be served by InputMethodManager
-        eventually(() ->
-                assertThat(imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+        // Wait for the new EditText to be served by InputMethodManager.
+        eventually(() -> assertThat(
+                imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+
         verifyInputViewStatusOnMainSync(() -> assertThat(
-                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
-                false /* inputViewStarted */);
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an implicit show request when the IME is not previously shown,
+     * and it should be shown in fullscreen mode, results in the IME not being shown.
+     */
+    @Test
+    public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Set orientation landscape to enable fullscreen mode.
+        setOrientation(2);
+        eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse());
+        // Wait for the TestActivity to be recreated.
+        eventually(() ->
+                assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+        // Get the new TestActivity.
+        mActivity = TestActivity.getLastCreatedInstance();
+        assertThat(mActivity).isNotNull();
+        InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+        // Wait for the new EditText to be served by InputMethodManager.
+        eventually(() -> assertThat(
+                imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+    }
+
+    /**
+     * This checks that an explicit show request when a hard keyboard is connected,
+     * results in the IME being shown.
+     */
+    @Test
+    public void testShowSoftInputExplicitly_withHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+    }
+
+    /**
+     * This checks that an implicit show request when a hard keyboard is connected,
+     * results in the IME not being shown.
+     */
     @Test
     public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
-        // When connecting to a hard keyboard and the flag is IMPLICIT, the Ime should not be shown.
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that an explicit show request followed by connecting a hard keyboard
+     * and a configuration change, still results in the IME being shown.
+     */
     @Test
-    public void testConfigurationChanged_withKeyboardShownExplicitly() throws InterruptedException {
+    public void testShowSoftInputExplicitly_thenConfigurationChanged() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Start with no hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
-        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
-        mInputMethodService.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
-        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
-                mInputMethodService.getResources().getConfiguration()),
-                true /* inputViewStarted */);
-    }
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-    @Test
-    public void testConfigurationChanged_withKeyboardShownImplicitly() throws InterruptedException {
-        verifyInputViewStatusOnMainSync(() -> assertThat(
-                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
-                true /* inputViewStarted */);
-        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
-        mInputMethodService.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        mInputMethodService.getResources().getConfiguration().orientation =
+                Configuration.ORIENTATION_LANDSCAPE;
+
+        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
+                mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+    }
+
+    /**
+     * This checks that an implicit show request followed by connecting a hard keyboard
+     * and a configuration change, does not trigger IMS#onFinishInputView,
+     * but results in the IME being hidden.
+     */
+    @Test
+    public void testShowSoftInputImplicitly_thenConfigurationChanged() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Start with no hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        mInputMethodService.getResources().getConfiguration().orientation =
+                Configuration.ORIENTATION_LANDSCAPE;
 
         // Normally, IMS#onFinishInputView will be called when finishing the input view by the user.
         // But if IMS#hideWindow is called when receiving a new configuration change, we don't
         // expect that it's user-driven to finish the lifecycle of input view with
         // IMS#onFinishInputView, because the input view will be re-initialized according to the
-        // last mShowSoftRequested state. So in this case we treat the input view is still alive.
+        // last #mShowInputRequested state. So in this case we treat the input view as still alive.
         verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
-                                mInputMethodService.getResources().getConfiguration()),
+                mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
                 true /* inputViewStarted */);
         assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
-    private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
-            throws InterruptedException {
-        verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
+    /**
+     * This checks that an explicit show request directly followed by an implicit show request,
+     * while a hardware keyboard is connected, still results in the IME being shown
+     * (i.e. the implicit show request is treated as explicit).
+     */
+    @Test
+    public void testShowSoftInputExplicitly_thenShowSoftInputImplicitly_withHardKeyboard()
+            throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Explicit show request.
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Implicit show request.
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        // This should now consider the implicit show request, but keep the state from the
+        // explicit show request, and thus not hide the keyboard.
+        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
+                        mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
-    private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted)
-            throws InterruptedException {
-        verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/);
+    /**
+     * This checks that a forced show request directly followed by an explicit show request,
+     * and then a hide not always request, still results in the IME being shown
+     * (i.e. the explicit show request retains the forced state).
+     */
+    @Test
+    public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways()
+            throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        verifyInputViewStatusOnMainSync(() ->
+                        mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that the IME fullscreen mode state is updated after changing orientation.
+     */
+    @Test
+    public void testFullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        Log.i(TAG, "Set orientation natural");
+        verifyFullscreenMode(() -> setOrientation(0),
+                false /* expected */,
+                true /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation left");
+        verifyFullscreenMode(() -> setOrientation(1),
+                true /* expected */,
+                false /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation right");
+        verifyFullscreenMode(() -> setOrientation(2),
+                false /* expected */,
+                false /* orientationPortrait */);
+    }
+
+    private void verifyInputViewStatus(
+            Runnable runnable, boolean expected, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+                false /* runOnMainSync */);
+    }
+
+    private void verifyInputViewStatusOnMainSync(
+            Runnable runnable, boolean expected, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+                true /* runOnMainSync */);
+    }
+
+    /**
+     * Verifies the status of the Input View after executing the given runnable.
+     *
+     * @param runnable the runnable to execute for showing or hiding the IME.
+     * @param expected whether the runnable is expected to trigger the signal.
+     * @param inputViewStarted the expected state of the Input View after executing the runnable.
+     * @param runOnMainSync whether to execute the runnable on the main thread.
+     */
     private void verifyInputViewStatusInternal(
-            Runnable runnable, boolean inputViewStarted, boolean runOnMainSync)
+            Runnable runnable, boolean expected, boolean inputViewStarted, boolean runOnMainSync)
             throws InterruptedException {
         CountDownLatch signal = new CountDownLatch(1);
         mInputMethodService.setCountDownLatchForTesting(signal);
-        // Runnable to trigger onStartInputView()/ onFinishInputView()
+        // Runnable to trigger onStartInputView() / onFinishInputView() / onConfigurationChanged()
         if (runOnMainSync) {
             mInstrumentation.runOnMainSync(runnable);
         } else {
             runnable.run();
         }
-        // Waits for onStartInputView() to finish.
         mInstrumentation.waitForIdleSync();
-        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (expected && !completed) {
+            fail("Timed out waiting for"
+                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+        } else if (!expected && completed) {
+            fail("Unexpected call"
+                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+        }
         // Input is not finished.
         assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
         assertThat(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
     }
 
-    @Test
-    public void testFullScreenMode() throws Exception {
-        Log.i(TAG, "Set orientation natural");
-        verifyFullscreenMode(() -> setOrientation(0), true /* orientationPortrait */);
-
-        Log.i(TAG, "Set orientation left");
-        verifyFullscreenMode(() -> setOrientation(1), false /* orientationPortrait */);
-
-        Log.i(TAG, "Set orientation right");
-        verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);
-    }
-
     private void setOrientation(int orientation) {
         // Simple wrapper for catching RemoteException.
         try {
@@ -366,7 +657,15 @@
         }
     }
 
-    private void verifyFullscreenMode(Runnable runnable, boolean orientationPortrait)
+    /**
+     * Verifies the IME fullscreen mode state after executing the given runnable.
+     *
+     * @param runnable the runnable to execute for setting the orientation.
+     * @param expected whether the runnable is expected to trigger the signal.
+     * @param orientationPortrait whether the orientation is expected to be portrait.
+     */
+    private void verifyFullscreenMode(
+            Runnable runnable, boolean expected, boolean orientationPortrait)
             throws InterruptedException {
         CountDownLatch signal = new CountDownLatch(1);
         mInputMethodService.setCountDownLatchForTesting(signal);
@@ -379,7 +678,12 @@
         }
         // Waits for onConfigurationChanged() to finish.
         mInstrumentation.waitForIdleSync();
-        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (expected && !completed) {
+            fail("Timed out waiting for onConfigurationChanged()");
+        } else if (!expected && completed) {
+            fail("Unexpected call onConfigurationChanged()");
+        }
 
         clickOnEditorText();
         eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isTrue());
@@ -416,7 +720,21 @@
         return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME;
     }
 
-    private String executeShellCommand(String cmd) throws Exception {
+    /**
+     * Sets the value of show_ime_with_hard_keyboard, only if it is different to the default value.
+     *
+     * @param enabled the value to be set.
+     */
+    private void setShowImeWithHardKeyboard(boolean enabled) throws IOException {
+        if (mShowImeWithHardKeyboardEnabled != enabled) {
+            executeShellCommand(enabled
+                    ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
+                    : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
+            mInstrumentation.waitForIdleSync();
+        }
+    }
+
+    private String executeShellCommand(String cmd) throws IOException {
         Log.i(TAG, "Run command: " + cmd);
         return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                 .executeShellCommand(cmd);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 869497c..3199e06 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -40,7 +40,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Display;
-import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -77,9 +76,9 @@
     public void testPerformShowIme() throws Exception {
         synchronized (ImfLock.class) {
             mVisibilityApplier.performShowIme(new Binder() /* showInputToken */,
-                    null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
+                    null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT);
         }
-        verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT);
+        verifyShowSoftInput(false, true, 0 /* showFlags */);
     }
 
     @Test
@@ -126,7 +125,7 @@
     @Test
     public void testApplyImeVisibility_showImeImplicit() throws Exception {
         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
-        verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT);
+        verifyShowSoftInput(true, true, 0 /* showFlags */);
     }
 
     @Test
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index a38c162..fae5f86 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -106,7 +106,7 @@
     @Test
     public void testRequestImeVisibility_showExplicit() {
         initImeTargetWindowState(mWindowToken);
-        boolean res = mComputer.onImeShowFlags(null, 0 /* show explicit */);
+        boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */);
         mComputer.requestImeVisibility(mWindowToken, res);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -118,6 +118,34 @@
         assertThat(mComputer.mRequestedShowExplicitly).isTrue();
     }
 
+    /**
+     * This checks that the state after an explicit show request does not get reset during
+     * a subsequent implicit show request, without an intermediary hide request.
+     */
+    @Test
+    public void testRequestImeVisibility_showExplicit_thenShowImplicit() {
+        initImeTargetWindowState(mWindowToken);
+        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+
+        mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+    }
+
+    /**
+     * This checks that the state after a forced show request does not get reset during
+     * a subsequent explicit show request, without an intermediary hide request.
+     */
+    @Test
+    public void testRequestImeVisibility_showForced_thenShowExplicit() {
+        initImeTargetWindowState(mWindowToken);
+        mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED);
+        assertThat(mComputer.mShowForced).isTrue();
+
+        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        assertThat(mComputer.mShowForced).isTrue();
+    }
+
     @Test
     public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() {
         // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index 42d373b..e87a34e 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -162,7 +163,10 @@
             assertThat(mBindingController.getCurToken()).isNotNull();
         }
         // Wait for onServiceConnected()
-        mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (!completed) {
+            fail("Timed out waiting for onServiceConnected()");
+        }
 
         // Verify onServiceConnected() is called and bound successfully.
         synchronized (ImfLock.class) {
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index 8d0e0c4..e1fd2b3 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -43,6 +43,8 @@
     },
     export_package_resources: true,
     sdk_version: "current",
+
+    certificate: "platform",
 }
 
 android_library {
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index 996322d..cf7d660 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.apps.inputmethod.simpleime">
 
-    <uses-sdk android:targetSdkVersion="31" />
-
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application android:debuggable="true"
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index f1ff338..a421db0 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -7,19 +7,12 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-// Include all test java files.
-filegroup {
-    name: "displayservicetests-sources",
-    srcs: [
-        "src/**/*.java",
-    ],
-}
-
 android_test {
     name: "DisplayServiceTests",
 
     srcs: [
         "src/**/*.java",
+        ":extended-mockito-rule-sources",
     ],
 
     libs: [
@@ -28,14 +21,15 @@
 
     static_libs: [
         "androidx.test.ext.junit",
-        "display-core-libs",
         "frameworks-base-testutils",
         "junit",
         "junit-params",
+        "mockingservicestests-utils-mockito",
         "platform-compat-test-rules",
         "platform-test-annotations",
         "services.core",
         "servicestests-utils",
+        "testables",
     ],
 
     defaults: [
@@ -56,10 +50,3 @@
         enabled: false,
     },
 }
-
-java_library {
-    name: "display-core-libs",
-    srcs: [
-        "src/com/android/server/display/TestUtils.java",
-    ],
-}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index d2bd10d..55fde00 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -17,10 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.frameworks.displayservicetests">
 
-    <!--
-    Insert permissions here. eg:
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    -->
+    <!-- Permissions -->
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
@@ -32,6 +29,10 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
+    <!-- Permissions needed for DisplayTransformManagerTest -->
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+
     <application android:debuggable="true"
                  android:testOnly="true">
         <uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java b/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
similarity index 96%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index 95c62ae..7e69357 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -86,7 +86,9 @@
                 .append("\n    brightnessReason:")
                 .append(displayBrightnessState.getBrightnessReason())
                 .append("\n    shouldUseAutoBrightness:")
-                .append(displayBrightnessState.getShouldUseAutoBrightness());
+                .append(displayBrightnessState.getShouldUseAutoBrightness())
+                .append("\n    isSlowChange:")
+                .append(displayBrightnessState.isSlowChange());
         return sb.toString();
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
index 081f19d..d8569f7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
@@ -21,8 +21,8 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.view.Display;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
@@ -46,7 +46,8 @@
         DisplayManagerInternal.DisplayPowerRequest
                 displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
         float brightnessToFollow = 0.2f;
-        mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow);
+        boolean slowChange = true;
+        mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow, slowChange);
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(BrightnessReason.REASON_FOLLOWER);
         DisplayBrightnessState expectedDisplayBrightnessState =
@@ -55,6 +56,7 @@
                         .setBrightnessReason(brightnessReason)
                         .setSdrBrightness(brightnessToFollow)
                         .setDisplayBrightnessStrategyName(mFollowerBrightnessStrategy.getName())
+                        .setIsSlowChange(slowChange)
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mFollowerBrightnessStrategy.updateBrightness(displayPowerRequest);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index e0bef1a..c280349 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -16,22 +16,49 @@
 
 package com.android.server.display.color;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.SurfaceControl;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.Arrays;
 
 public class DisplayWhiteBalanceTintControllerTest {
+    @Mock
+    private Context mMockedContext;
+    @Mock
+    private Resources mMockedResources;
+    @Mock
+    private DisplayManagerInternal mDisplayManagerInternal;
 
-    private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
+    private MockitoSession mSession;
+    private Resources mResources;
+    IBinder mDisplayToken;
+    DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
 
     @Before
     public void setUp() {
@@ -40,6 +67,47 @@
                 new DisplayWhiteBalanceTintController(displayManagerInternal);
         mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
         mDisplayWhiteBalanceTintController.setActivated(true);
+
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .mockStatic(SurfaceControl.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+
+        mResources = InstrumentationRegistry.getContext().getResources();
+        // These Resources are common to all tests.
+        doReturn(4000)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
+        doReturn(8000)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+        doReturn(new int[] {0})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+        doReturn(new int[] {20})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
+        doReturn(mMockedResources).when(mMockedContext).getResources();
+
+        mDisplayToken = new Binder();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
     }
 
     @Test
@@ -98,4 +166,204 @@
                     })
                 ).isTrue();
     }
+
+
+    /**
+     * Setup should succeed when SurfaceControl setup results in a valid color transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithSurfaceControl() {
+        // Make SurfaceControl return sRGB primaries
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+    }
+
+    /**
+     * Setup should fail when SurfaceControl setup results in an invalid color transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
+        // Make SurfaceControl return invalid display primaries
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with invalid SurfaceControl succeeded")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isFalse();
+    }
+
+    /**
+     * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
+     * transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithResources() {
+        // Use default (valid) Resources
+        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        // Make SurfaceControl setup fail
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid Resources failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+    }
+
+    /**
+     * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
+     * transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithInvalidResources() {
+        // Use Resources with invalid color data
+        doReturn(new String[] {
+                "0", "0", "0", // Red X, Y, Z
+                "0", "0", "0", // Green X, Y, Z
+                "0", "0", "0", // Blue X, Y, Z
+                "0", "0", "0", // White X, Y, Z
+        })
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        // Make SurfaceControl setup fail
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+        setUpTintController();
+        assertWithMessage("Setup with invalid Resources succeeded")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isFalse();
+    }
+
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setMatrix(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(
+                mDisplayWhiteBalanceTintController.getTargetCct());
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setTargetCct(cct);
+        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
+    private void setUpTintController() {
+        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+                mDisplayManagerInternal);
+        mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
+        mDisplayWhiteBalanceTintController.setActivated(true);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 101498a..bde02e8 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -115,3 +115,14 @@
         "android.test.runner",
     ],
 }
+
+filegroup {
+    name: "extended-mockito-rule-sources",
+    srcs: [
+        "src/com/android/server/ExtendedMockitoRule.java",
+        "src/com/android/server/Visitor.java",
+    ],
+    visibility: [
+        "//frameworks/base/services/tests/displayservicetests",
+    ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS b/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
deleted file mode 100644
index 6ce1ee4..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
deleted file mode 100644
index 3faf394..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.color;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.Binder;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.CieXyz;
-import android.view.SurfaceControl.DisplayPrimaries;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@RunWith(AndroidJUnit4.class)
-public class DisplayWhiteBalanceTintControllerTest {
-    @Mock
-    private Context mMockedContext;
-    @Mock
-    private Resources mMockedResources;
-    @Mock
-    private DisplayManagerInternal mDisplayManagerInternal;
-
-    private MockitoSession mSession;
-    private Resources mResources;
-    IBinder mDisplayToken;
-    DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
-
-    @Before
-    public void setUp() {
-        mSession = ExtendedMockito.mockitoSession()
-                .initMocks(this)
-                .mockStatic(SurfaceControl.class)
-                .strictness(Strictness.LENIENT)
-                .startMocking();
-
-        mResources = InstrumentationRegistry.getContext().getResources();
-        // These Resources are common to all tests.
-        doReturn(4000)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
-        doReturn(8000)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
-        doReturn(6500)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
-        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
-                .when(mMockedResources)
-                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
-        doReturn(6500)
-                .when(mMockedResources)
-                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
-        doReturn(new int[] {0})
-                .when(mMockedResources)
-                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
-        doReturn(new int[] {20})
-                .when(mMockedResources)
-                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
-
-        doReturn(mMockedResources).when(mMockedContext).getResources();
-
-        mDisplayToken = new Binder();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mSession != null) {
-            mSession.finishMocking();
-        }
-    }
-
-    /**
-     * Setup should succeed when SurfaceControl setup results in a valid color transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithSurfaceControl() {
-        // Make SurfaceControl return sRGB primaries
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-    }
-
-    /**
-     * Setup should fail when SurfaceControl setup results in an invalid color transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
-        // Make SurfaceControl return invalid display primaries
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.white = new CieXyz();
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with invalid SurfaceControl succeeded")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isFalse();
-    }
-
-    /**
-     * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
-     * transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithResources() {
-        // Use default (valid) Resources
-        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
-        // Make SurfaceControl setup fail
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid Resources failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-    }
-
-    /**
-     * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
-     * transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithInvalidResources() {
-        // Use Resources with invalid color data
-        doReturn(new String[] {
-                "0", "0", "0", // Red X, Y, Z
-                "0", "0", "0", // Green X, Y, Z
-                "0", "0", "0", // Blue X, Y, Z
-                "0", "0", "0", // White X, Y, Z
-            })
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
-        // Make SurfaceControl setup fail
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
-        setUpTintController();
-        assertWithMessage("Setup with invalid Resources succeeded")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isFalse();
-    }
-
-    /**
-     * Matrix should match the precalculated one for given cct and display primaries.
-     */
-    @Test
-    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-
-        final int cct = 6500;
-        mDisplayWhiteBalanceTintController.setMatrix(cct);
-        mDisplayWhiteBalanceTintController.setAppliedCct(
-                mDisplayWhiteBalanceTintController.getTargetCct());
-
-        assertWithMessage("Failed to set temperature")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(cct);
-        float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
-        final float[] expectedMatrixDwb = {
-            0.971848f,   -0.001421f,  0.000491f, 0.0f,
-            0.028193f,    0.945798f,  0.003207f, 0.0f,
-            -0.000042f,  -0.000989f,  0.988659f, 0.0f,
-            0.0f,         0.0f,       0.0f,      1.0f
-        };
-        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
-            1e-6f /* tolerance */);
-    }
-
-    /**
-     * Matrix should match the precalculated one for given cct and display primaries.
-     */
-    @Test
-    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-
-        final int cct = 6500;
-        mDisplayWhiteBalanceTintController.setTargetCct(cct);
-        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
-        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
-
-        assertWithMessage("Failed to set temperature")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(cct);
-        final float[] expectedMatrixDwb = {
-                0.971848f,   -0.001421f,  0.000491f, 0.0f,
-                0.028193f,    0.945798f,  0.003207f, 0.0f,
-                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
-                0.0f,         0.0f,       0.0f,      1.0f
-        };
-        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
-                1e-6f /* tolerance */);
-    }
-
-    private void setUpTintController() {
-        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
-                mDisplayManagerInternal);
-        mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
-        mDisplayWhiteBalanceTintController.setActivated(true);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index e457119..e7777f7 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -42,6 +42,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionConsentManager;
 import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.pm.UserManagerInternal;
 
@@ -91,6 +92,8 @@
 
     @Mock private RemoteContentProtectionService mMockRemoteContentProtectionService;
 
+    @Mock private ContentProtectionConsentManager mMockContentProtectionConsentManager;
+
     private boolean mDevCfgEnableContentProtectionReceiver;
 
     private int mContentProtectionBlocklistManagersCreated;
@@ -99,6 +102,8 @@
 
     private int mRemoteContentProtectionServicesCreated;
 
+    private int mContentProtectionConsentManagersCreated;
+
     private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
 
     private boolean mContentProtectionServiceInfoConstructorShouldThrow;
@@ -114,43 +119,51 @@
     }
 
     @Test
-    public void constructor_contentProtection_flagDisabled_noBlocklistManager() {
+    public void constructor_contentProtection_flagDisabled_noManagers() {
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
         verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
-    public void constructor_contentProtection_componentNameNull_noBlocklistManager() {
+    public void constructor_contentProtection_componentNameNull_noManagers() {
         mConfigDefaultContentProtectionService = null;
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
         verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
-    public void constructor_contentProtection_componentNameBlank_noBlocklistManager() {
+    public void constructor_contentProtection_componentNameBlank_noManagers() {
         mConfigDefaultContentProtectionService = "   ";
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
         verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
-    public void constructor_contentProtection_enabled_createsBlocklistManager() {
+    public void constructor_contentProtection_enabled_createsManagers() {
         mDevCfgEnableContentProtectionReceiver = true;
 
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
         assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
         verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
@@ -175,11 +188,13 @@
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isNull();
-        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -211,11 +226,13 @@
         assertThat(actual.contentProtectionOptions).isNotNull();
         assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
         assertThat(actual.whitelistedComponents).isNull();
-        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -234,7 +251,22 @@
     }
 
     @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionNotGranted() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
     public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
@@ -248,6 +280,7 @@
 
     @Test
     public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -271,11 +304,27 @@
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionNotGranted() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
     public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
 
@@ -289,6 +338,7 @@
 
     @Test
     public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
         when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -312,16 +362,18 @@
                         USER_ID, COMPONENT_NAME);
 
         assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
     @Test
-    public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
+    public void isContentProtectionReceiverEnabled_withoutManagers() {
         boolean actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
@@ -336,6 +388,7 @@
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
     }
 
@@ -423,5 +476,11 @@
             mRemoteContentProtectionServicesCreated++;
             return mMockRemoteContentProtectionService;
         }
+
+        @Override
+        protected ContentProtectionConsentManager createContentProtectionConsentManager() {
+            mContentProtectionConsentManagersCreated++;
+            return mMockContentProtectionConsentManager;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
new file mode 100644
index 0000000..0ffa891
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.contentcapture"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
new file mode 100644
index 0000000..0e80bfd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.TestableContentResolver;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionConsentManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionConsentManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionConsentManagerTest {
+
+    private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
+
+    private static final Uri URI_PACKAGE_VERIFIER_USER_CONSENT =
+            Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT);
+
+    private static final int VALUE_TRUE = 1;
+
+    private static final int VALUE_FALSE = -1;
+
+    private static final int VALUE_DEFAULT = 0;
+
+    private static final int TEST_USER_ID = 1234;
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public final TestableContext mTestableContext =
+            new TestableContext(ApplicationProvider.getApplicationContext());
+
+    private final TestableContentResolver mTestableContentResolver =
+            mTestableContext.getContentResolver();
+
+    @Mock private ContentResolver mMockContentResolver;
+
+    @Mock private DevicePolicyManagerInternal mMockDevicePolicyManagerInternal;
+
+    @Test
+    public void constructor_registersContentObserver() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(mMockContentResolver);
+
+        assertThat(manager.mContentObserver).isNotNull();
+        verify(mMockContentResolver)
+                .registerContentObserver(
+                        URI_PACKAGE_VERIFIER_USER_CONSENT,
+                        /* notifyForDescendants= */ false,
+                        manager.mContentObserver,
+                        UserHandle.USER_ALL);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierNotGranted() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_FALSE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierGranted_userNotManaged() {
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierGranted_userManaged() {
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierDefault() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_DEFAULT);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+    }
+
+    @Test
+    public void contentObserver() throws Exception {
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+
+        Settings.Global.putInt(
+                mTestableContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_FALSE);
+        // Observer has to be called manually, mTestableContentResolver is not propagating
+        manager.mContentObserver.onChange(
+                /* selfChange= */ false, URI_PACKAGE_VERIFIER_USER_CONSENT, TEST_USER_ID);
+        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(firstActual).isTrue();
+        assertThat(secondActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+    }
+
+    private ContentProtectionConsentManager createContentProtectionConsentManager(
+            ContentResolver contentResolver) {
+        return new ContentProtectionConsentManager(
+                new Handler(Looper.getMainLooper()),
+                contentResolver,
+                mMockDevicePolicyManagerInternal);
+    }
+
+    private ContentProtectionConsentManager createContentProtectionConsentManager(
+            int valuePackageVerifierUserConsent) {
+        Settings.Global.putInt(
+                mTestableContentResolver,
+                KEY_PACKAGE_VERIFIER_USER_CONSENT,
+                valuePackageVerifierUserConsent);
+        return createContentProtectionConsentManager(mTestableContentResolver);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
new file mode 100644
index 0000000..419508c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.contentprotection"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
index d5ad815..b5bf1ea 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -16,7 +16,11 @@
 
 package com.android.server.dreams;
 
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
+import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS;
+
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
@@ -32,7 +36,9 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IPowerManager;
 import android.os.IRemoteCallback;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.service.dreams.IDreamService;
@@ -58,6 +64,8 @@
 
     @Mock
     private ActivityTaskManager mActivityTaskManager;
+    @Mock
+    private IPowerManager mPowerManager;
 
     @Mock
     private IBinder mIBinder;
@@ -67,6 +75,8 @@
     @Captor
     private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor;
     @Captor
+    private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
+    @Captor
     private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor;
 
     private final TestLooper mLooper = new TestLooper();
@@ -90,6 +100,12 @@
         when(mContext.getSystemServiceName(ActivityTaskManager.class))
                 .thenReturn(Context.ACTIVITY_TASK_SERVICE);
 
+        final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null);
+        when(mContext.getSystemService(Context.POWER_SERVICE))
+                .thenReturn(powerManager);
+        when(mContext.getSystemServiceName(PowerManager.class))
+                .thenReturn(Context.POWER_SERVICE);
+
         mToken = new Binder();
         mDreamName = ComponentName.unflattenFromString("dream");
         mOverlayName = ComponentName.unflattenFromString("dream_overlay");
@@ -209,9 +225,51 @@
         verify(mIDreamService).detach();
     }
 
+    @Test
+    public void serviceDisconnect_resetsScreenTimeout() throws RemoteException {
+        // Start dream.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+        ServiceConnection serviceConnection = captureServiceConnection();
+        serviceConnection.onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Dream disconnects unexpectedly.
+        serviceConnection.onServiceDisconnected(mDreamName);
+        mLooper.dispatchAll();
+
+        // Power manager receives user activity signal.
+        verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(),
+                eq(USER_ACTIVITY_EVENT_OTHER),
+                eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS));
+    }
+
+    @Test
+    public void binderDied_resetsScreenTimeout() throws RemoteException {
+        // Start dream.
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+        captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Dream binder dies.
+        captureDeathRecipient().binderDied();
+        mLooper.dispatchAll();
+
+        // Power manager receives user activity signal.
+        verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(),
+                eq(USER_ACTIVITY_EVENT_OTHER),
+                eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS));
+    }
+
     private ServiceConnection captureServiceConnection() {
         verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(),
                 any());
         return mServiceConnectionACaptor.getValue();
     }
+
+    private IBinder.DeathRecipient captureDeathRecipient() throws RemoteException {
+        verify(mIBinder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+        return mDeathRecipientCaptor.getValue();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a109d5c..0292bca 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -81,7 +81,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
@@ -406,7 +405,6 @@
     UriGrantsManagerInternal mUgmInternal;
     @Mock
     AppOpsManager mAppOpsManager;
-    private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener;
     @Mock
     private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
             mNotificationAssistantAccessGrantedCallback;
@@ -606,12 +604,6 @@
         tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
                 SEARCH_SELECTOR_PKG);
 
-        doAnswer(invocation -> {
-            mOnPermissionChangeListener = invocation.getArgument(2);
-            return null;
-        }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(),
-                any());
-
         mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
         mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -3224,6 +3216,48 @@
     }
 
     @Test
+    public void testUpdateAppNotifyCreatorBlock() throws Exception {
+        when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+
+        mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
+        Thread.sleep(500);
+        waitForIdle();
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+    }
+
+    @Test
+    public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception {
+        when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+
+        mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
+        verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
+    }
+
+    @Test
+    public void testUpdateAppNotifyCreatorUnblock() throws Exception {
+        when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+
+        mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true);
+        Thread.sleep(500);
+        waitForIdle();
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+    }
+
+    @Test
     public void testUpdateChannelNotifyCreatorBlock() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -11169,8 +11203,6 @@
                 ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
         // Given: a notification from an app on the system partition has the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11186,8 +11218,6 @@
     public void fixMediaNotification_withOnGoingFlag_shouldBeNonDismissible()
             throws Exception {
         // Given: a media notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .setStyle(new Notification.MediaStyle()
@@ -11216,8 +11246,6 @@
                 ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
         // Given: a notification from an app on the system partition has the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11233,9 +11261,6 @@
     public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
             throws Exception {
         // Given: a call notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
-
         Person person = new Person.Builder()
                 .setName("caller")
                 .build();
@@ -11256,8 +11281,6 @@
     @Test
     public void fixNonExemptNotification_withOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11274,8 +11297,6 @@
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_NO_DISMISS set (even though this is
         // not allowed)
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .build();
         n.flags |= Notification.FLAG_NO_DISMISS;
@@ -11290,8 +11311,6 @@
     @Test
     public void fixMediaNotification_withoutOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -11310,8 +11329,6 @@
             throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set,
         // but has the flag FLAG_NO_DISMISS set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -11330,8 +11347,6 @@
     public void fixNonExempt_Notification_withoutOnGoingFlag_shouldBeDismissible()
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .build();
@@ -11348,8 +11363,6 @@
             throws Exception {
         when(mDevicePolicyManager.isActiveDeviceOwner(mUid)).thenReturn(true);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -11376,8 +11389,6 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -11397,8 +11408,6 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -12139,134 +12148,6 @@
                 any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
     }
 
-    @Test
-    public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage()
-            throws RemoteException {
-        // Have preexisting posted notifications from revoked package and other packages.
-        mService.addNotification(new NotificationRecord(mContext,
-                generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel));
-        mService.addNotification(new NotificationRecord(mContext,
-                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
-        // Have preexisting enqueued notifications from revoked package and other packages.
-        mService.addEnqueuedNotification(new NotificationRecord(mContext,
-                generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel));
-        mService.addEnqueuedNotification(new NotificationRecord(mContext,
-                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
-        assertThat(mService.mNotificationList).hasSize(2);
-        assertThat(mService.mEnqueuedNotifications).hasSize(2);
-
-        when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001);
-        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
-
-        mOnPermissionChangeListener.onOpChanged(
-                AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0);
-        waitForIdle();
-
-        assertThat(mService.mNotificationList).hasSize(1);
-        assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other");
-        assertThat(mService.mEnqueuedNotifications).hasSize(1);
-        assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo(
-                "other");
-    }
-
-    @Test
-    public void onOpChanged_permissionStillGranted_notificationsAreNotAffected()
-            throws RemoteException {
-        // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission
-        // being now granted, AND having previously posted notifications from said package) should
-        // never happen (if we trust the broadcasts are correct). So this test is for a what-if
-        // scenario, to verify we still handle it reasonably.
-
-        // Have preexisting posted notifications from specific package and other packages.
-        mService.addNotification(new NotificationRecord(mContext,
-                generateSbn("granted", 1001, 1, 0), mTestNotificationChannel));
-        mService.addNotification(new NotificationRecord(mContext,
-                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
-        // Have preexisting enqueued notifications from specific package and other packages.
-        mService.addEnqueuedNotification(new NotificationRecord(mContext,
-                generateSbn("granted", 1001, 3, 0), mTestNotificationChannel));
-        mService.addEnqueuedNotification(new NotificationRecord(mContext,
-                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
-        assertThat(mService.mNotificationList).hasSize(2);
-        assertThat(mService.mEnqueuedNotifications).hasSize(2);
-
-        when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001);
-        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
-
-        mOnPermissionChangeListener.onOpChanged(
-                AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0);
-        waitForIdle();
-
-        assertThat(mService.mNotificationList).hasSize(2);
-        assertThat(mService.mEnqueuedNotifications).hasSize(2);
-    }
-
-    @Test
-    public void onOpChanged_permissionGranted_notifiesAppUnblocked() throws Exception {
-        when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
-        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
-
-        mOnPermissionChangeListener.onOpChanged(
-                AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
-        waitForIdle();
-        mTestableLooper.moveTimeForward(500);
-        waitForIdle();
-
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-        assertThat(captor.getValue().getAction()).isEqualTo(
-                NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
-        assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
-        assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)).isFalse();
-    }
-
-    @Test
-    public void onOpChanged_permissionRevoked_notifiesAppBlocked() throws Exception {
-        when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
-        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
-
-        mOnPermissionChangeListener.onOpChanged(
-                AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
-        waitForIdle();
-        mTestableLooper.moveTimeForward(500);
-        waitForIdle();
-
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-        assertThat(captor.getValue().getAction()).isEqualTo(
-                NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
-        assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
-        assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)).isTrue();
-    }
-
-    @Test
-    public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception {
-        mService.addNotification(new NotificationRecord(mContext,
-                generateSbn("package", 1001, 1, 0), mTestNotificationChannel));
-        assertThat(mService.mNotificationList).hasSize(1);
-        when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001);
-        when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn(
-                true);
-
-        // Start with granted permission and simulate effect of revoking it.
-        when(mPermissionHelper.hasPermission(1001)).thenReturn(true);
-        doAnswer(invocation -> {
-            when(mPermissionHelper.hasPermission(1001)).thenReturn(false);
-            mOnPermissionChangeListener.onOpChanged(
-                    AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
-            return null;
-        }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true);
-
-        mBinderService.setNotificationsEnabledForPackage("package", 1001, false);
-        waitForIdle();
-
-        assertThat(mService.mNotificationList).hasSize(0);
-
-        mTestableLooper.moveTimeForward(500);
-        waitForIdle();
-        verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null));
-    }
-
     private static <T extends Parcelable> T parcelAndUnparcel(T source,
             Parcelable.Creator<T> creator) {
         Parcel parcel = Parcel.obtain();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index dedb8f1..3ee75de 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -771,7 +771,7 @@
         mZenModeHelper.mConfig = null; // will evaluate config to zen mode off
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenMode("test", true);
+            mZenModeHelper.evaluateZenModeLocked("test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -798,7 +798,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenMode("test", true);
+            mZenModeHelper.evaluateZenModeLocked("test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -825,7 +825,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenMode("test", true);
+            mZenModeHelper.evaluateZenModeLocked("test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -2269,7 +2269,7 @@
         // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off
         // given that we don't have any zen rules active.
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.evaluateZenMode("test", true);
+        mZenModeHelper.evaluateZenModeLocked("test", true);
 
         // Check that the change actually took: zen mode should be off now
         assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 5c3102d..65e77dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -182,12 +182,12 @@
 
     @Test
     public void testLaunchState() {
-        final ToIntFunction<Boolean> launchTemplate = doRelaunch -> {
+        final ToIntFunction<Runnable> launchTemplate = action -> {
             clearInvocations(mLaunchObserver);
             onActivityLaunched(mTopActivity);
             notifyTransitionStarting(mTopActivity);
-            if (doRelaunch) {
-                mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity);
+            if (action != null) {
+                action.run();
             }
             final ActivityMetricsLogger.TransitionInfoSnapshot info =
                     notifyWindowsDrawn(mTopActivity);
@@ -199,21 +199,27 @@
         // Assume that the process is started (ActivityBuilder has mocked the returned value of
         // ATMS#getProcessController) but the activity has not attached process.
         mTopActivity.app = null;
-        assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+        assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(null))
                 .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
 
         mTopActivity.app = app;
         mNewActivityCreated = false;
-        assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+        assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(null))
                 .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
 
-        assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */))
+        assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(
+                () -> mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity)))
                 .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH);
 
+        assertWithMessage("Cold launch by restart").that(launchTemplate.applyAsInt(
+                () -> mActivityMetricsLogger.notifyBindApplication(
+                        mTopActivity.info.applicationInfo)))
+                .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
+
         mTopActivity.app = null;
         mNewActivityCreated = true;
         doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid);
-        assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+        assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(null))
                 .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 3934b02..4034dbc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -62,28 +62,31 @@
     @Test
     public void testPostLayout() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        statusBar.setBounds(0, 0, 500, 1000);
         statusBar.getFrame().set(0, 0, 500, 100);
         statusBar.mHasSurface = true;
         mProvider.setWindowContainer(statusBar, null, null);
         mProvider.updateSourceFrame(statusBar.getFrame());
         mProvider.onPostLayout();
         assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame());
-        assertEquals(Insets.of(0, 100, 0, 0),
-                mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                        false /* ignoreVisibility */));
-        assertEquals(Insets.of(0, 100, 0, 0),
-                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+        assertEquals(Insets.of(0, 100, 0, 0), mProvider.getInsetsHint());
+
+        // Change the bounds and call onPostLayout. Make sure the insets hint gets updated.
+        statusBar.setBounds(0, 10, 500, 1000);
+        mProvider.onPostLayout();
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
     }
 
     @Test
     public void testPostLayout_invisible() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        statusBar.setBounds(0, 0, 500, 1000);
         statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindowContainer(statusBar, null, null);
         mProvider.updateSourceFrame(statusBar.getFrame());
         mProvider.onPostLayout();
-        assertEquals(Insets.NONE, mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                false /* ignoreVisibility */));
+        assertTrue(mProvider.getSource().getFrame().isEmpty());
+        assertEquals(Insets.NONE, mProvider.getInsetsHint());
     }
 
     @Test
@@ -160,6 +163,36 @@
     }
 
     @Test
+    public void testUpdateSourceFrame() {
+        final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        mProvider.setWindowContainer(statusBar, null, null);
+        statusBar.setBounds(0, 0, 500, 1000);
+
+        mProvider.setServerVisible(true);
+        statusBar.getFrame().set(0, 0, 500, 100);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 100, 0, 0), mProvider.getInsetsHint());
+
+        // Only change the source frame but not the visibility.
+        statusBar.getFrame().set(0, 0, 500, 90);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
+
+        mProvider.setServerVisible(false);
+        statusBar.getFrame().set(0, 0, 500, 80);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertTrue(mProvider.getSource().getFrame().isEmpty());
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
+
+        // Only change the visibility but not the frame.
+        mProvider.setServerVisible(true);
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 80, 0, 0), mProvider.getInsetsHint());
+    }
+
+    @Test
     public void testUpdateSourceFrameForIme() {
         final WindowState inputMethod = createWindow(null, TYPE_INPUT_METHOD, "inputMethod");
 
@@ -184,7 +217,7 @@
     }
 
     @Test
-    public void testInsetsModified() {
+    public void testSetRequestedVisibleTypes() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
@@ -196,7 +229,7 @@
     }
 
     @Test
-    public void testInsetsModified_noControl() {
+    public void testSetRequestedVisibleTypes_noControl() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index b02b774..df0808f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1315,6 +1315,26 @@
         assertTrue(info.supportsMultiWindow);
     }
 
+    @Test
+    public void testRemoveCompatibleRecentTask() {
+        final Task task1 = createTaskBuilder(".Task").setWindowingMode(
+                WINDOWING_MODE_FULLSCREEN).build();
+        mRecentTasks.add(task1);
+        final Task task2 = createTaskBuilder(".Task").setWindowingMode(
+                WINDOWING_MODE_MULTI_WINDOW).build();
+        mRecentTasks.add(task2);
+        assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+
+        // Set windowing mode and ensure the same fullscreen task that created earlier is removed.
+        task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mRecentTasks.removeCompatibleRecentTask(task2);
+        assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+        assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId);
+    }
+
     private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
         HardwareBuffer buffer = null;
         if (bufferSize != null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index abf21a5..7eab06a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -656,7 +656,7 @@
                 topSplitPrimary.getVisibility(null /* starting */));
         // Make primary split root transient-hide.
         spyOn(splitPrimary.mTransitionController);
-        doReturn(true).when(splitPrimary.mTransitionController).isTransientHide(
+        doReturn(true).when(splitPrimary.mTransitionController).isTransientVisible(
                 organizer.mPrimary);
         // The split root and its top become visible.
         assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 3908947..d5afe3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -27,6 +27,11 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
@@ -91,9 +96,12 @@
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -2255,6 +2263,169 @@
     }
 
     @Test
+    public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+    }
+
+    @Test
+    public void testUserOverrideSplitScreenAspectRatioForPortraitDisplay() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+    }
+
+    @Test
+    public void testUserOverrideDisplaySizeAspectRatioForLandscapeDisplay() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayWidth / displayHeight;
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+    }
+
+    @Test
+    public void testUserOverrideDisplaySizeAspectRatioForPortraitDisplay() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayHeight / displayWidth;
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+    }
+
+    @Test
+    public void testUserOverride32AspectRatioForPortraitDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
+        testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
+    }
+
+    @Test
+    public void testUserOverride32AspectRatioForLandscapeDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+        testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
+    }
+
+    @Test
+    public void testUserOverride43AspectRatioForPortraitDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
+        testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
+    }
+
+    @Test
+    public void testUserOverride43AspectRatioForLandscapeDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+        testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
+    }
+
+    @Test
+    public void testUserOverride169AspectRatioForPortraitDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1800, /* dh */ 1500);
+        testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
+    }
+
+    @Test
+    public void testUserOverride169AspectRatioForLandscapeDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1500, /* dh */ 1800);
+        testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testUserOverrideAspectRatioOverSystemOverride() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_PORTRAIT,
+                3 / 2f,
+                USER_MIN_ASPECT_RATIO_3_2,
+                true);
+    }
+
+    @Test
+    public void testUserOverrideAspectRatioNotEnabled() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+
+        // App aspect ratio doesn't change
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_PORTRAIT,
+                1f * 1600 / 1400,
+                USER_MIN_ASPECT_RATIO_3_2,
+                false);
+    }
+
+    private void testUserOverrideAspectRatio(float expectedAspectRatio,
+            @PackageManager.UserMinAspectRatio int aspectRatio) {
+        testUserOverrideAspectRatio(true,
+                SCREEN_ORIENTATION_PORTRAIT,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_PORTRAIT,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+
+        testUserOverrideAspectRatio(true,
+                SCREEN_ORIENTATION_LANDSCAPE,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_LANDSCAPE,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+    }
+
+    private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation,
+            float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio,
+            boolean enabled) {
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration
+                .setUserAppAspectRatioSettingsOverrideEnabled(enabled);
+        // Set user aspect ratio override
+        final IPackageManager pm = mAtm.getPackageManager();
+        try {
+            doReturn(aspectRatio).when(pm)
+                    .getUserMinAspectRatio(activity.packageName, activity.mUserId);
+        } catch (RemoteException ignored) {
+        }
+
+        prepareLimitedBounds(activity, screenOrientation, isUnresizable);
+
+        final Rect afterBounds = activity.getBounds();
+        final int width = afterBounds.width();
+        final int height = afterBounds.height();
+        final float afterAspectRatio =
+                (float) Math.max(width, height) / (float) Math.min(width, height);
+
+        assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
     public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d4fabf4..e91fdde 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1484,6 +1484,47 @@
     }
 
     @Test
+    public void testIsTransientVisible() {
+        final ActivityRecord appB = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setVisible(false).build();
+        final ActivityRecord recent = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setVisible(false).build();
+        final ActivityRecord appA = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task taskA = appA.getTask();
+        final Task taskB = appB.getTask();
+        final Task taskRecent = recent.getTask();
+        registerTestTransitionPlayer();
+        final TransitionController controller = mRootWindowContainer.mTransitionController;
+        final Transition transition = createTestTransition(TRANSIT_OPEN, controller);
+        controller.moveToCollecting(transition);
+        transition.collect(recent);
+        transition.collect(taskA);
+        transition.setTransientLaunch(recent, taskA);
+        taskRecent.moveToFront("move-recent-to-front");
+
+        // During collecting and playing, the recent is on top so it is visible naturally.
+        // While B needs isTransientVisible to keep visibility because it is occluded by recents.
+        assertFalse(controller.isTransientVisible(taskB));
+        assertTrue(controller.isTransientVisible(taskA));
+        assertFalse(controller.isTransientVisible(taskRecent));
+        // Switch to playing state.
+        transition.onTransactionReady(transition.getSyncId(), mMockT);
+        assertTrue(controller.isTransientVisible(taskA));
+
+        // Switch to another task. For example, use gesture navigation to switch tasks.
+        taskB.moveToFront("move-b-to-front");
+        // The previous app (taskA) should be paused first so it loses transient visible. Because
+        // visually it is taskA -> taskB, the pause -> resume order should be the same.
+        assertFalse(controller.isTransientVisible(taskA));
+        // Keep the recent visible so there won't be 2 activities pausing at the same time. It is
+        // to avoid the latency to resume the current top, i.e. appB.
+        assertTrue(controller.isTransientVisible(taskRecent));
+        // The recent is paused after the transient transition is finished.
+        controller.finishTransition(transition);
+        assertFalse(controller.isTransientVisible(taskRecent));
+    }
+
+    @Test
     public void testNotReadyPushPop() {
         final TransitionController controller = new TestTransitionController(mAtm);
         controller.setSyncEngine(mWm.mSyncEngine);
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 0c267b2..320daee 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -17,8 +17,9 @@
 package com.android.inputmethod.stresstest;
 
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 
+import static com.android.compatibility.common.util.SystemUtil.eventually;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
@@ -26,11 +27,16 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.Intent;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.UiDevice;
 import android.widget.EditText;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,6 +45,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test IME visibility by using system default IME to ensure the behavior is consistent
@@ -59,8 +66,12 @@
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
 
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3);
+
     private static final int NUM_TEST_ITERATIONS = 10;
 
+    private final boolean mIsPortrait;
+
     @Parameterized.Parameters(name = "isPortrait={0}")
     public static List<Boolean> isPortraitCases() {
         // Test in both portrait and landscape mode.
@@ -68,6 +79,7 @@
     }
 
     public DefaultImeVisibilityTest(boolean isPortrait) {
+        mIsPortrait = isPortrait;
         mImeStressTestRule.setIsPortrait(isPortrait);
     }
 
@@ -75,14 +87,26 @@
     public void showHideDefaultIme() {
         Intent intent =
                 createIntent(
-                        0x0, /* No window focus flags */
-                        SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE,
+                        0x0 /* No window focus flags */,
+                        SOFT_INPUT_STATE_HIDDEN | SOFT_INPUT_ADJUST_RESIZE,
                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
         ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent);
         EditText editText = activity.getEditText();
+
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        eventually(
+                () ->
+                        assertWithMessage("Display rotation should be updated.")
+                                .that(uiDevice.getDisplayRotation())
+                                .isEqualTo(mIsPortrait ? 0 : 1),
+                TIMEOUT);
+
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
-            callOnMainSync(activity::showImeWithInputMethodManager);
+            // TODO(b/291752364): Remove the explicit focus request once the issue with view focus
+            //  change between fullscreen IME and actual editText is fixed.
+            callOnMainSync(editText::requestFocus);
             verifyWindowAndViewFocus(editText, true, true);
+            callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntilImeIsShown(editText);
 
             callOnMainSync(activity::hideImeWithInputMethodManager);
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
index 12104b2..c746321 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
@@ -20,9 +20,9 @@
 import android.os.RemoteException;
 import android.support.test.uiautomator.UiDevice;
 
+import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import org.checkerframework.checker.nullness.qual.NonNull;
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 
@@ -35,8 +35,6 @@
 public class ImeStressTestRule extends TestWatcher {
     private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true";
     private static final String LOCK_SCREEN_ON_COMMAND = "locksettings set-disabled false";
-    private static final String SET_PORTRAIT_MODE_COMMAND = "settings put system user_rotation 0";
-    private static final String SET_LANDSCAPE_MODE_COMMAND = "settings put system user_rotation 1";
     private static final String SIMPLE_IME_ID =
             "com.android.apps.inputmethod.simpleime/.SimpleInputMethodService";
     private static final String ENABLE_IME_COMMAND = "ime enable " + SIMPLE_IME_ID;
@@ -44,8 +42,10 @@
     private static final String DISABLE_IME_COMMAND = "ime disable " + SIMPLE_IME_ID;
     private static final String RESET_IME_COMMAND = "ime reset";
 
-    @NonNull private final Instrumentation mInstrumentation;
-    @NonNull private final UiDevice mUiDevice;
+    @NonNull
+    private final Instrumentation mInstrumentation;
+    @NonNull
+    private final UiDevice mUiDevice;
     // Whether the screen orientation is set to portrait.
     private boolean mIsPortrait;
     // Whether to use a simple test Ime or system default Ime for test.
@@ -105,12 +105,13 @@
     private void setOrientation() {
         try {
             mUiDevice.freezeRotation();
-            executeShellCommand(
-                    mIsPortrait ? SET_PORTRAIT_MODE_COMMAND : SET_LANDSCAPE_MODE_COMMAND);
-        } catch (IOException e) {
-            throw new RuntimeException("Could not set screen orientation.", e);
+            if (mIsPortrait) {
+                mUiDevice.setOrientationNatural();
+            } else {
+                mUiDevice.setOrientationLeft();
+            }
         } catch (RemoteException e) {
-            throw new RuntimeException("Could not freeze rotation.", e);
+            throw new RuntimeException("Could not freeze rotation or set screen orientation.", e);
         }
     }
 
@@ -147,7 +148,8 @@
         }
     }
 
-    private @NonNull String executeShellCommand(@NonNull String cmd) throws IOException {
+    @NonNull
+    private String executeShellCommand(@NonNull String cmd) throws IOException {
         return mUiDevice.executeShellCommand(cmd);
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index f3c8194..c9b5c96 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -392,7 +392,7 @@
         public boolean showImeWithInputMethodManager() {
             boolean showResult =
                     getInputMethodManager()
-                            .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
+                            .showSoftInput(mEditText, 0 /* flags */);
             if (showResult) {
                 Log.i(TAG, "IMM#showSoftInput successfully");
             } else {
@@ -404,7 +404,8 @@
         /** Hide IME with InputMethodManager. */
         public boolean hideImeWithInputMethodManager() {
             boolean hideResult =
-                    getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+                    getInputMethodManager()
+                            .hideSoftInputFromWindow(mEditText.getWindowToken(), 0 /* flags */);
             if (hideResult) {
                 Log.i(TAG, "IMM#hideSoftInput successfully");
             } else {