Merge "Collect embedded TaskFragment window changes" into tm-qpr-dev
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7a88a057..5ca8b05 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3920,6 +3920,12 @@
      * that has been started.  This is sent as a foreground
      * broadcast, since it is part of a visible user interaction; be as quick
      * as possible when handling it.
+     *
+     * <p>
+     * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+     * received. For example, the user could have been removed, started or stopped already,
+     * regardless of which broadcast you receive. Because of that, receivers should always check
+     * the current state of the user.
      * @hide
      */
     public static final String ACTION_USER_STARTED =
@@ -3937,6 +3943,12 @@
      * {@link #ACTION_USER_STOPPING}.  It is <b>not</b> generally safe to use with
      * other user state broadcasts since those are foreground broadcasts so can
      * execute in a different order.
+     *
+     * <p>
+     * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+     * received. For example, the user could have been removed, started or stopped already,
+     * regardless of which broadcast you receive. Because of that, receivers should always check
+     * the current state of the user.
      * @hide
      */
     public static final String ACTION_USER_STARTING =
@@ -3955,6 +3967,11 @@
      * {@link #ACTION_USER_STARTING}.  It is <b>not</b> generally safe to use with
      * other user state broadcasts since those are foreground broadcasts so can
      * execute in a different order.
+     * <p>
+     * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+     * received. For example, the user could have been removed, started or stopped already,
+     * regardless of which broadcast you receive. Because of that, receivers should always check
+     * the current state of the user.
      * @hide
      */
     public static final String ACTION_USER_STOPPING =
@@ -3967,6 +3984,12 @@
      * specific package.  This is only sent to registered receivers, not manifest
      * receivers.  It is sent to all running users <em>except</em> the one that
      * has just been stopped (which is no longer running).
+     *
+     * <p>
+     * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+     * received. For example, the user could have been removed, started or stopped already,
+     * regardless of which broadcast you receive. Because of that, receivers should always check
+     * the current state of the user.
      * @hide
      */
     @TestApi
@@ -3999,6 +4022,12 @@
      * It is sent to all running users.
      * You must hold
      * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
+     *
+     * <p>
+     * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+     * received. For example, the user could have been removed, started or stopped already,
+     * regardless of which broadcast you receive. Because of that, receivers should always check
+     * the current state of the user.
      * @hide
      */
     @SystemApi
@@ -4009,6 +4038,12 @@
      * Broadcast Action: Sent when the credential-encrypted private storage has
      * become unlocked for the target user. This is only sent to registered
      * receivers, not manifest receivers.
+     *
+     * <p>
+     * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+     * received. For example, the user could have been removed, started or stopped already,
+     * regardless of which broadcast you receive. Because of that, receivers should always check
+     * the current state of the user.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 7e355d9..e845ffa 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -934,6 +934,12 @@
      * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
      * Otherwise, this method might throw an exception or return {@code null}.
      *
+     * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #getParcelable(String)} instead.
+     *
      * @param key a String, or {@code null}
      * @param clazz The type of the object expected
      * @return a Parcelable value, or {@code null}
@@ -990,6 +996,13 @@
      * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
      * Otherwise, this method might throw an exception or return {@code null}.
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #getParcelableArray(String)} instead.
+     *
      * @param key a String, or {@code null}
      * @param clazz The type of the items inside the array. This is only verified when unparceling.
      * @return a Parcelable[] value, or {@code null}
@@ -1054,6 +1067,13 @@
      * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
      * Otherwise, this method might throw an exception or return {@code null}.
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #getParcelableArrayList(String)} instead.
+     *
      * @param key   a String, or {@code null}
      * @param clazz The type of the items inside the array list. This is only verified when
      *     unparceling.
@@ -1105,6 +1125,13 @@
      *     <li>The object is not of type {@code clazz}.
      * </ul>
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #getSparseParcelableArray(String)} instead.
+     *
      * @param key a String, or null
      * @param clazz The type of the items inside the sparse array. This is only verified when
      *     unparceling.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2664f05..3d70138 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3235,6 +3235,13 @@
      * Same as {@link #readList(List, ClassLoader)} but accepts {@code clazz} parameter as
      * the type required for each item.
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readList(List, ClassLoader)} instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there was an error
      * trying to instantiate an element.
@@ -3463,6 +3470,13 @@
      * Same as {@link #readArrayList(ClassLoader)} but accepts {@code clazz} parameter as
      * the type required for each item.
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readArrayList(ClassLoader)} instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there was an error
      * trying to instantiate an element.
@@ -3497,6 +3511,13 @@
      * Same as {@link #readArray(ClassLoader)} but accepts {@code clazz} parameter as
      * the type required for each item.
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readArray(ClassLoader)} instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there was an error
      * trying to instantiate an element.
@@ -3530,6 +3551,13 @@
      * Same as {@link #readSparseArray(ClassLoader)} but accepts {@code clazz} parameter as
      * the type required for each item.
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readSparseArray(ClassLoader)} instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there was an error
      * trying to instantiate an element.
@@ -3847,6 +3875,13 @@
      * Same as {@link #readParcelableList(List, ClassLoader)} but accepts {@code clazz} parameter as
      * the type required for each item.
      *
+     * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+     * the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readParcelableList(List, ClassLoader)} instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there was an error
      * trying to instantiate an element.
@@ -4744,6 +4779,12 @@
      * Same as {@link #readParcelable(ClassLoader)} but accepts {@code clazz} parameter as the type
      * required for each item.
      *
+     * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readParcelable(ClassLoader)} instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there was an error
      * trying to instantiate an element.
@@ -4812,6 +4853,12 @@
      * Same as {@link #readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
      * as the required type.
      *
+     * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readParcelableCreator(ClassLoader) instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there there was an error
      * trying to read the {@link Parcelable.Creator}.
@@ -4939,6 +4986,12 @@
      * Same as {@link #readParcelableArray(ClassLoader)}  but accepts {@code clazz} parameter as
      * the type required for each item.
      *
+     * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+     * enclosing class of the runtime type of its CREATOR field (that is,
+     * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+     * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+     * CREATOR, use the deprecated {@link #readParcelableArray(ClassLoader)} instead.
+     *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children classes or there was an error
      * trying to instantiate an element.
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c102ad3..c198098 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -349,20 +349,6 @@
         return insets;
     }
 
-    // TODO: Remove this once the task bar is treated as navigation bar.
-    public Insets calculateInsetsWithInternalTypes(Rect frame, @InternalInsetsType int[] types,
-            boolean ignoreVisibility) {
-        Insets insets = Insets.NONE;
-        for (int i = types.length - 1; i >= 0; i--) {
-            InsetsSource source = mSources[types[i]];
-            if (source == null) {
-                continue;
-            }
-            insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
-        }
-        return insets;
-    }
-
     public Insets calculateInsets(Rect frame, @InsetsType int types,
             InsetsVisibilities overrideVisibilities) {
         Insets insets = Insets.NONE;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e673041..ec6b4ac 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -284,7 +284,7 @@
      * @hide
      */
     public static final boolean LOCAL_LAYOUT =
-            SystemProperties.getBoolean("persist.debug.local_layout", true);
+            SystemProperties.getBoolean("persist.debug.local_layout", false);
 
     /**
      * Set this system property to true to force the view hierarchy to render
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index fbabf52..a66b405 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -63,6 +63,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.drawable.AnimatedVectorDrawable;
@@ -146,6 +147,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Supplier;
 
 /**
@@ -281,6 +283,7 @@
     private long mQueriedSharingShortcutsTimeMs;
 
     private int mCurrAvailableWidth = 0;
+    private Insets mLastAppliedInsets = null;
     private int mLastNumberOfChildren = -1;
     private int mMaxTargetsPerRow = 1;
 
@@ -1619,6 +1622,8 @@
         if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
             mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
         }
+        mPersonalAppPredictor = null;
+        mWorkAppPredictor = null;
     }
 
     @Override // ResolverListCommunicator
@@ -2531,7 +2536,11 @@
                 || gridAdapter.calculateChooserTargetWidth(availableWidth)
                 || recyclerView.getAdapter() == null
                 || availableWidth != mCurrAvailableWidth;
+
+        boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
+
         if (isLayoutUpdated
+                || insetsChanged
                 || mLastNumberOfChildren != recyclerView.getChildCount()) {
             mCurrAvailableWidth = availableWidth;
             if (isLayoutUpdated) {
@@ -2552,7 +2561,7 @@
                 return;
             }
 
-            if (mLastNumberOfChildren == recyclerView.getChildCount()) {
+            if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
                 return;
             }
 
@@ -2563,6 +2572,7 @@
                 int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
                 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
                 mEnterTransitionAnimationDelegate.markOffsetCalculated();
+                mLastAppliedInsets = mSystemWindowInsets;
             });
         }
     }
@@ -3055,7 +3065,12 @@
             mChooserMultiProfilePagerAdapter.setupContainerPadding(
                     getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container));
         }
-        return super.onApplyWindowInsets(v, insets);
+
+        WindowInsets result = super.onApplyWindowInsets(v, insets);
+        if (mResolverDrawerLayout != null) {
+            mResolverDrawerLayout.requestLayout();
+        }
+        return result;
     }
 
     private void setHorizontalScrollingEnabled(boolean enabled) {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 1ec5325..e57b90a 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -43,6 +43,7 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 import com.android.internal.app.chooser.ChooserTargetInfo;
 import com.android.internal.app.chooser.DisplayResolveInfo;
@@ -86,7 +87,7 @@
     private final ChooserActivityLogger mChooserActivityLogger;
 
     private int mNumShortcutResults = 0;
-    private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+    private final Map<TargetInfo, AsyncTask> mIconLoaders = new HashMap<>();
     private boolean mApplySharingAppLimits;
 
     // Reserve spots for incoming direct share targets by adding placeholders
@@ -104,6 +105,8 @@
     private AppPredictor mAppPredictor;
     private AppPredictor.Callback mAppPredictorCallback;
 
+    private LoadDirectShareIconTaskProvider mTestLoadDirectShareTaskProvider;
+
     // For pinned direct share labels, if the text spans multiple lines, the TextView will consume
     // the full width, even if the characters actually take up less than that. Measure the actual
     // line widths and constrain the View's width based upon that so that the pin doesn't end up
@@ -240,7 +243,6 @@
         mListViewDataChanged = false;
     }
 
-
     private void createPlaceHolders() {
         mNumShortcutResults = 0;
         mServiceTargets.clear();
@@ -265,31 +267,25 @@
             return;
         }
 
-        if (!(info instanceof DisplayResolveInfo)) {
-            holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
-            holder.bindIcon(info);
-
-            if (info instanceof SelectableTargetInfo) {
-                // direct share targets should append the application name for a better readout
-                DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
-                CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
-                CharSequence extendedInfo = info.getExtendedInfo();
-                String contentDescription = String.join(" ", info.getDisplayLabel(),
-                        extendedInfo != null ? extendedInfo : "", appName);
-                holder.updateContentDescription(contentDescription);
-            }
-        } else {
+        if (info instanceof DisplayResolveInfo) {
             DisplayResolveInfo dri = (DisplayResolveInfo) info;
             holder.bindLabel(dri.getDisplayLabel(), dri.getExtendedInfo(), alwaysShowSubLabel());
-            LoadIconTask task = mIconLoaders.get(dri);
-            if (task == null) {
-                task = new LoadIconTask(dri, holder);
-                mIconLoaders.put(dri, task);
-                task.execute();
+            startDisplayResolveInfoIconLoading(holder, dri);
+        } else {
+            holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
+
+            if (info instanceof SelectableTargetInfo) {
+                SelectableTargetInfo selectableInfo = (SelectableTargetInfo) info;
+                // direct share targets should append the application name for a better readout
+                DisplayResolveInfo rInfo = selectableInfo.getDisplayResolveInfo();
+                CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
+                CharSequence extendedInfo = selectableInfo.getExtendedInfo();
+                String contentDescription = String.join(" ", selectableInfo.getDisplayLabel(),
+                        extendedInfo != null ? extendedInfo : "", appName);
+                holder.updateContentDescription(contentDescription);
+                startSelectableTargetInfoIconLoading(holder, selectableInfo);
             } else {
-                // The holder was potentially changed as the underlying items were
-                // reshuffled, so reset the target holder
-                task.setViewHolder(holder);
+                holder.bindIcon(info);
             }
         }
 
@@ -330,6 +326,32 @@
         }
     }
 
+    private void startDisplayResolveInfoIconLoading(ViewHolder holder, DisplayResolveInfo info) {
+        LoadIconTask task = (LoadIconTask) mIconLoaders.get(info);
+        if (task == null) {
+            task = new LoadIconTask(info, holder);
+            mIconLoaders.put(info, task);
+            task.execute();
+        } else {
+            // The holder was potentially changed as the underlying items were
+            // reshuffled, so reset the target holder
+            task.setViewHolder(holder);
+        }
+    }
+
+    private void startSelectableTargetInfoIconLoading(
+            ViewHolder holder, SelectableTargetInfo info) {
+        LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info);
+        if (task == null) {
+            task = mTestLoadDirectShareTaskProvider == null
+                    ? new LoadDirectShareIconTask(info)
+                    : mTestLoadDirectShareTaskProvider.get();
+            mIconLoaders.put(info, task);
+            task.loadIcon();
+        }
+        task.setViewHolder(holder);
+    }
+
     void updateAlphabeticalList() {
         new AsyncTask<Void, Void, List<DisplayResolveInfo>>() {
             @Override
@@ -344,7 +366,7 @@
                 Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
                 for (DisplayResolveInfo info : allTargets) {
                     String resolvedTarget = info.getResolvedComponentName().getPackageName()
-                        + '#' + info.getDisplayLabel();
+                            + '#' + info.getDisplayLabel();
                     DisplayResolveInfo multiDri = consolidated.get(resolvedTarget);
                     if (multiDri == null) {
                         consolidated.put(resolvedTarget, info);
@@ -353,7 +375,7 @@
                     } else {
                         // create consolidated target from the single DisplayResolveInfo
                         MultiDisplayResolveInfo multiDisplayResolveInfo =
-                            new MultiDisplayResolveInfo(resolvedTarget, multiDri);
+                                new MultiDisplayResolveInfo(resolvedTarget, multiDri);
                         multiDisplayResolveInfo.addTarget(info);
                         consolidated.put(resolvedTarget, multiDisplayResolveInfo);
                     }
@@ -740,10 +762,24 @@
     }
 
     /**
+     * An alias for onBindView to use with unit tests.
+     */
+    @VisibleForTesting
+    public void testViewBind(View view, TargetInfo info, int position) {
+        onBindView(view, info, position);
+    }
+
+    @VisibleForTesting
+    public void setTestLoadDirectShareTaskProvider(LoadDirectShareIconTaskProvider provider) {
+        mTestLoadDirectShareTaskProvider = provider;
+    }
+
+    /**
      * Necessary methods to communicate between {@link ChooserListAdapter}
      * and {@link ChooserActivity}.
      */
-    interface ChooserListCommunicator extends ResolverListCommunicator {
+    @VisibleForTesting
+    public interface ChooserListCommunicator extends ResolverListCommunicator {
 
         int getMaxRankedTargets();
 
@@ -751,4 +787,59 @@
 
         boolean isSendAction(Intent targetIntent);
     }
+
+    /**
+     * Loads direct share targets icons.
+     */
+    @VisibleForTesting
+    public class LoadDirectShareIconTask extends AsyncTask<Void, Void, Void> {
+        private final SelectableTargetInfo mTargetInfo;
+        private ViewHolder mViewHolder;
+
+        private LoadDirectShareIconTask(SelectableTargetInfo targetInfo) {
+            mTargetInfo = targetInfo;
+        }
+
+        @Override
+        protected Void doInBackground(Void... voids) {
+            mTargetInfo.loadIcon();
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void arg) {
+            if (mViewHolder != null) {
+                mViewHolder.bindIcon(mTargetInfo);
+                notifyDataSetChanged();
+            }
+        }
+
+        /**
+         * Specifies a view holder that will be updated when the task is completed.
+         */
+        public void setViewHolder(ViewHolder viewHolder) {
+            mViewHolder = viewHolder;
+            mViewHolder.bindIcon(mTargetInfo);
+            notifyDataSetChanged();
+        }
+
+        /**
+         * An alias for execute to use with unit tests.
+         */
+        public void loadIcon() {
+            execute();
+        }
+    }
+
+    /**
+     * An interface for the unit tests to override icon loading task creation
+     */
+    @VisibleForTesting
+    public interface LoadDirectShareIconTaskProvider {
+        /**
+         * Provides an instance of the task.
+         * @return
+         */
+        LoadDirectShareIconTask get();
+    }
 }
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 66fff5c..3a3baa7 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -834,7 +834,11 @@
         void onHandlePackagesChanged(ResolverListAdapter listAdapter);
     }
 
-    static class ViewHolder {
+    /**
+     * A view holder.
+     */
+    @VisibleForTesting
+    public static class ViewHolder {
         public View itemView;
         public Drawable defaultItemViewBackground;
 
@@ -842,7 +846,8 @@
         public TextView text2;
         public ImageView icon;
 
-        ViewHolder(View view) {
+        @VisibleForTesting
+        public ViewHolder(View view) {
             itemView = view;
             defaultItemViewBackground = view.getBackground();
             text = (TextView) view.findViewById(com.android.internal.R.id.text1);
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 264e4f7..37eab40 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -37,6 +37,7 @@
 import android.text.SpannableStringBuilder;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
@@ -59,8 +60,11 @@
     private final String mDisplayLabel;
     private final PackageManager mPm;
     private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator;
+    @GuardedBy("this")
+    private ShortcutInfo mShortcutInfo;
     private Drawable mBadgeIcon = null;
     private CharSequence mBadgeContentDescription;
+    @GuardedBy("this")
     private Drawable mDisplayIcon;
     private final Intent mFillInIntent;
     private final int mFillInFlags;
@@ -78,6 +82,7 @@
         mModifiedScore = modifiedScore;
         mPm = mContext.getPackageManager();
         mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator;
+        mShortcutInfo = shortcutInfo;
         mIsPinned = shortcutInfo != null && shortcutInfo.isPinned();
         if (sourceInfo != null) {
             final ResolveInfo ri = sourceInfo.getResolveInfo();
@@ -92,8 +97,6 @@
                 }
             }
         }
-        // TODO(b/121287224): do this in the background thread, and only for selected targets
-        mDisplayIcon = getChooserTargetIconDrawable(chooserTarget, shortcutInfo);
 
         if (sourceInfo != null) {
             mBackupResolveInfo = null;
@@ -118,7 +121,10 @@
         mChooserTarget = other.mChooserTarget;
         mBadgeIcon = other.mBadgeIcon;
         mBadgeContentDescription = other.mBadgeContentDescription;
-        mDisplayIcon = other.mDisplayIcon;
+        synchronized (other) {
+            mShortcutInfo = other.mShortcutInfo;
+            mDisplayIcon = other.mDisplayIcon;
+        }
         mFillInIntent = fillInIntent;
         mFillInFlags = flags;
         mModifiedScore = other.mModifiedScore;
@@ -141,6 +147,25 @@
         return mSourceInfo;
     }
 
+    /**
+     * Load display icon, if needed.
+     */
+    public void loadIcon() {
+        ShortcutInfo shortcutInfo;
+        Drawable icon;
+        synchronized (this) {
+            shortcutInfo = mShortcutInfo;
+            icon = mDisplayIcon;
+        }
+        if (icon == null && shortcutInfo != null) {
+            icon = getChooserTargetIconDrawable(mChooserTarget, shortcutInfo);
+            synchronized (this) {
+                mDisplayIcon = icon;
+                mShortcutInfo = null;
+            }
+        }
+    }
+
     private Drawable getChooserTargetIconDrawable(ChooserTarget target,
             @Nullable ShortcutInfo shortcutInfo) {
         Drawable directShareIcon = null;
@@ -270,7 +295,7 @@
     }
 
     @Override
-    public Drawable getDisplayIcon(Context context) {
+    public synchronized Drawable getDisplayIcon(Context context) {
         return mDisplayIcon;
     }
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 483efd86c..d5f40e0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1567,8 +1567,7 @@
     <bool name="config_enableIdleScreenBrightnessMode">false</bool>
 
     <!-- Array of desired screen brightness in nits corresponding to the lux values
-         in the config_autoBrightnessLevels array. As with config_screenBrightnessMinimumNits and
-         config_screenBrightnessMaximumNits, the display brightness is defined as the measured
+         in the config_autoBrightnessLevels array. The display brightness is defined as the measured
          brightness of an all-white image.
 
          If this is defined then:
@@ -1589,7 +1588,7 @@
     <array name="config_autoBrightnessDisplayValuesNitsIdle">
     </array>
 
-    <!-- Array of output values for button backlight corresponding to the luX values
+    <!-- Array of output values for button backlight corresponding to the lux values
          in the config_autoBrightnessLevels array.  This array should have size one greater
          than the size of the config_autoBrightnessLevels array.
          The brightness values must be between 0 and 255 and be non-decreasing.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 71919ff..e773a9c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -79,6 +79,11 @@
   <java-symbol type="id" name="deny_button" />
   <java-symbol type="id" name="description" />
   <java-symbol type="id" name="divider" />
+  <java-symbol type="id" name="drag" />
+  <java-symbol type="id" name="profile_pager" />
+  <java-symbol type="id" name="chooser_header" />
+  <java-symbol type="id" name="content_preview_container" />
+  <java-symbol type="id" name="profile_tabhost" />
   <java-symbol type="id" name="edit_query" />
   <java-symbol type="id" name="edittext_container" />
   <java-symbol type="id" name="expand_activities_button" />
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
new file mode 100644
index 0000000..f027e0b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.service.chooser.ChooserTarget
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.R
+import com.android.internal.app.chooser.SelectableTargetInfo
+import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator
+import com.android.server.testutils.any
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+class ChooserListAdapterTest {
+    private val packageManager = mock<PackageManager> {
+        whenever(resolveActivity(any(), anyInt())).thenReturn(mock())
+    }
+    private val context = InstrumentationRegistry.getInstrumentation().getContext()
+    private val resolverListController = mock<ResolverListController>()
+    private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> {
+        whenever(maxRankedTargets).thenReturn(0)
+    }
+    private val selectableTargetInfoCommunicator =
+        mock<SelectableTargetInfoCommunicator> {
+            whenever(targetIntent).thenReturn(mock())
+        }
+    private val chooserActivityLogger = mock<ChooserActivityLogger>()
+
+    private val testSubject = ChooserListAdapter(
+        context,
+        emptyList(),
+        emptyArray(),
+        emptyList(),
+        false,
+        resolverListController,
+        chooserListCommunicator,
+        selectableTargetInfoCommunicator,
+        packageManager,
+        chooserActivityLogger
+    )
+
+    @Test
+    fun testDirectShareTargetLoadingIconIsStarted() {
+        val view = createView()
+        val viewHolder = ResolverListAdapter.ViewHolder(view)
+        view.tag = viewHolder
+        val targetInfo = createSelectableTargetInfo()
+        val iconTask = mock<ChooserListAdapter.LoadDirectShareIconTask> {
+            doNothing().whenever(this).loadIcon()
+        }
+        testSubject.setTestLoadDirectShareTaskProvider(
+            mock {
+                whenever(get()).thenReturn(iconTask)
+            }
+        )
+        testSubject.testViewBind(view, targetInfo, 0)
+
+        verify(iconTask, times(1)).loadIcon()
+        verify(iconTask, times(1)).setViewHolder(viewHolder)
+    }
+
+    @Test
+    fun testOnlyOneTaskPerTarget() {
+        val view = createView()
+        val viewHolderOne = ResolverListAdapter.ViewHolder(view)
+        view.tag = viewHolderOne
+        val targetInfo = createSelectableTargetInfo()
+        val iconTaskOne = mock<ChooserListAdapter.LoadDirectShareIconTask> {
+            doNothing().whenever(this).loadIcon()
+        }
+        val testTaskProvider = mock<ChooserListAdapter.LoadDirectShareIconTaskProvider> {
+            whenever(get()).thenReturn(iconTaskOne)
+        }
+        testSubject.setTestLoadDirectShareTaskProvider(
+            testTaskProvider
+        )
+        testSubject.testViewBind(view, targetInfo, 0)
+
+        val viewHolderTwo = ResolverListAdapter.ViewHolder(view)
+        view.tag = viewHolderTwo
+        whenever(testTaskProvider.get()).thenReturn(mock())
+
+        testSubject.testViewBind(view, targetInfo, 0)
+
+        verify(iconTaskOne, times(1)).loadIcon()
+        verify(iconTaskOne, times(1)).setViewHolder(viewHolderOne)
+        verify(iconTaskOne, times(1)).setViewHolder(viewHolderTwo)
+        verify(testTaskProvider, times(1)).get()
+    }
+
+    private fun createSelectableTargetInfo(): SelectableTargetInfo =
+        SelectableTargetInfo(
+            context,
+            null,
+            createChooserTarget(),
+            1f,
+            selectableTargetInfoCommunicator,
+            null
+        )
+
+    private fun createChooserTarget(): ChooserTarget =
+        ChooserTarget(
+            "Title",
+            null,
+            1f,
+            ComponentName("package", "package.Class"),
+            Bundle()
+        )
+
+    private fun createView(): View {
+        val view = FrameLayout(context)
+        TextView(context).apply {
+            id = R.id.text1
+            view.addView(this)
+        }
+        TextView(context).apply {
+            id = R.id.text2
+            view.addView(this)
+        }
+        ImageView(context).apply {
+            id = R.id.icon
+            view.addView(this)
+        }
+        return view
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index d5d4935..b5409b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -695,15 +696,19 @@
     /**
      * Create a {@link WindowContainerTransaction} to clear task bounds.
      *
+     * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+     *
      * @param displayId display id for tasks that will have bounds cleared
      * @return {@link WindowContainerTransaction} with pending operations to clear bounds
      */
-    public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
+    public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
         WindowContainerTransaction wct = new WindowContainerTransaction();
         for (int i = 0; i < mTasks.size(); i++) {
             RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
-            if (taskInfo.displayId == displayId) {
+            if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
+                    == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
                 ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
                         taskInfo.token, taskInfo);
                 wct.setBounds(taskInfo.token, null);
@@ -715,17 +720,21 @@
     /**
      * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
      *
+     * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+     *
      * @param displayId display id for tasks that will have windowing mode reset to {@link
      *                  WindowConfiguration#WINDOWING_MODE_UNDEFINED}
      * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
      */
-    public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
+    public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
         WindowContainerTransaction wct = new WindowContainerTransaction();
         for (int i = 0; i < mTasks.size(); i++) {
             RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
             if (taskInfo.displayId == displayId
-                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
                 ProtoLog.d(WM_SHELL_DESKTOP_MODE,
                         "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
                         taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 295035f..e7533bcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -73,14 +73,15 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         // Reset freeform windowing mode that is set per task level (tasks should inherit
         // container value)
-        wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
+        wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
+                true /* transfer */);
         int targetWindowingMode;
         if (active) {
             targetWindowingMode = WINDOWING_MODE_FREEFORM;
         } else {
             targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
             // Clear any resized bounds
-            wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
+            wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
                     true /* transfer */);
         }
         wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b46eff6..297c79e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1306,6 +1306,12 @@
             return;
         }
 
+        if (mLeash == null || !mLeash.isValid()) {
+            Log.e(TAG, String.format("scheduleFinishResizePip with null leash! mState=%d",
+                  mPipTransitionState.getTransitionState()));
+            return;
+        }
+
         finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds,
                 direction, -1);
         if (updateBoundsCallback != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 8d405f4..199007d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -589,17 +589,17 @@
                     return;
                 }
 
-                // The surfaces of splitting tasks were placed with window bounds when preparing the
-                // transition, so update divider surface separately.
-                final RemoteAnimationTarget dividerTarget = getDividerBarLegacyTarget();
-                mSplitLayout.getRefDividerBounds(mTempRect1);
-                t.setLayer(dividerTarget.leash, Integer.MAX_VALUE)
-                        .setPosition(dividerTarget.leash, mTempRect1.left, mTempRect1.top);
-                setDividerVisibility(true, t);
+                // Wrap the divider bar into non-apps target to animate together.
+                nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+                        getDividerBarLegacyTarget());
 
+                updateSurfaceBounds(mSplitLayout, t, false);
+                setDividerVisibility(true, t);
                 for (int i = 0; i < apps.length; ++i) {
                     if (apps[i].mode == MODE_OPENING) {
                         t.show(apps[i].leash);
+                        // Reset the surface position of the opening app to prevent double-offset.
+                        t.setPosition(apps[i].leash, 0, 0);
                     }
                 }
                 t.apply();
@@ -614,9 +614,8 @@
                         };
                 Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
                 try {
-                    adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
-                            ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
-                                    dividerTarget), wrapCallback);
+                    adapter.getRunner().onAnimationStart(
+                            transit, apps, wallpapers, nonApps, wrapCallback);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error starting remote animation", e);
                 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index f50097d..7680f4d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -43,7 +43,7 @@
 /**
  * Test entering pip while changing orientation (from app in landscape to pip window in portrait)
  *
- * To run this test: `atest EnterPipToOtherOrientationTest:EnterPipToOtherOrientationTest`
+ * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
  *
  * Actions:
  *     Launch [testApp] on a fixed portrait orientation
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index b29c436..7cbace5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -637,26 +639,22 @@
     }
 
     @Test
-    public void testPrepareClearBoundsForTasks() {
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED);
-        task1.displayId = 1;
+    public void testPrepareClearBoundsForStandardTasks() {
         MockToken token1 = new MockToken();
-        task1.token = token1.token();
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
         mOrganizer.onTaskAppeared(task1, null);
 
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED);
-        task2.displayId = 1;
         MockToken token2 = new MockToken();
-        task2.token = token2.token();
+        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
         mOrganizer.onTaskAppeared(task2, null);
 
-        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED);
-        otherDisplayTask.displayId = 2;
         MockToken otherDisplayToken = new MockToken();
-        otherDisplayTask.token = otherDisplayToken.token();
+        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
+                otherDisplayToken);
+        otherDisplayTask.displayId = 2;
         mOrganizer.onTaskAppeared(otherDisplayTask, null);
 
-        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1);
+        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
 
         assertEquals(wct.getChanges().size(), 2);
         Change boundsChange1 = wct.getChanges().get(token1.binder());
@@ -673,26 +671,40 @@
     }
 
     @Test
-    public void testPrepareClearFreeformForTasks() {
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM);
-        task1.displayId = 1;
+    public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
         MockToken token1 = new MockToken();
-        task1.token = token1.token();
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
         mOrganizer.onTaskAppeared(task1, null);
 
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
-        task2.displayId = 1;
         MockToken token2 = new MockToken();
-        task2.token = token2.token();
+        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
+        task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
         mOrganizer.onTaskAppeared(task2, null);
 
-        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM);
-        otherDisplayTask.displayId = 2;
+        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
+
+        // Only clear bounds for task1
+        assertEquals(1, wct.getChanges().size());
+        assertNotNull(wct.getChanges().get(token1.binder()));
+    }
+
+    @Test
+    public void testPrepareClearFreeformForStandardTasks() {
+        MockToken token1 = new MockToken();
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
+        mOrganizer.onTaskAppeared(task1, null);
+
+        MockToken token2 = new MockToken();
+        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
+        mOrganizer.onTaskAppeared(task2, null);
+
         MockToken otherDisplayToken = new MockToken();
-        otherDisplayTask.token = otherDisplayToken.token();
+        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
+                otherDisplayToken);
+        otherDisplayTask.displayId = 2;
         mOrganizer.onTaskAppeared(otherDisplayTask, null);
 
-        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1);
+        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
 
         // Only task with freeform windowing mode and the right display should be updated
         assertEquals(wct.getChanges().size(), 1);
@@ -701,6 +713,24 @@
         assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
     }
 
+    @Test
+    public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
+        MockToken token1 = new MockToken();
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
+        mOrganizer.onTaskAppeared(task1, null);
+
+        MockToken token2 = new MockToken();
+        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
+        task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
+        mOrganizer.onTaskAppeared(task2, null);
+
+        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
+
+        // Only clear freeform for task1
+        assertEquals(1, wct.getChanges().size());
+        assertNotNull(wct.getChanges().get(token1.binder()));
+    }
+
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
@@ -708,6 +738,14 @@
         return taskInfo;
     }
 
+    private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
+        RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
+        taskInfo.displayId = 1;
+        taskInfo.token = token.token();
+        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+        return taskInfo;
+    }
+
     private static class MockToken {
         private final WindowContainerToken mToken;
         private final IBinder mBinder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 7afef8b..ef532e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -88,8 +88,8 @@
         WindowContainerTransaction taskWct = new WindowContainerTransaction();
         MockToken taskMockToken = new MockToken();
         taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
-        when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
-                taskWct);
+        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
+                mContext.getDisplayId())).thenReturn(taskWct);
 
         // Create a fake WCT to simulate setting display windowing mode to freeform
         WindowContainerTransaction displayWct = new WindowContainerTransaction();
@@ -126,15 +126,15 @@
         WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
         MockToken taskWmMockToken = new MockToken();
         taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
-        when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
-                taskWmWct);
+        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
+                mContext.getDisplayId())).thenReturn(taskWmWct);
 
         // Create a fake WCT to simulate clearing task bounds
         WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
         MockToken taskBoundsMockToken = new MockToken();
         taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
-        when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn(
-                taskBoundsWct);
+        when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
+                mContext.getDisplayId())).thenReturn(taskBoundsWct);
 
         // Create a fake WCT to simulate setting display windowing mode to fullscreen
         WindowContainerTransaction displayWct = new WindowContainerTransaction();
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 3a8e559..687e4dd 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -179,7 +179,7 @@
 void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) {
     int fast_i = (mNumFastRects - 1) * 4;
     int janky_i = (mNumJankyRects - 1) * 4;
-    ;
+
     for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
         if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
             continue;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 976117b..75d3ff7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -512,9 +512,19 @@
 
     ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
 
-    const auto drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
-                                                  &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                                  mLightInfo, mRenderNodes, &(profiler()));
+    IRenderPipeline::DrawResult drawResult;
+    {
+        // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
+        // or it can lead to memory corruption.
+        // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
+        // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
+        // the thread we're primarily concerned about being responsive, this being too broad
+        // shouldn't pose a performance issue.
+        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
+                                           &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
+                                           mLightInfo, mRenderNodes, &(profiler()));
+    }
 
     uint64_t frameCompleteNr = getFrameNumber();
 
@@ -754,11 +764,11 @@
     FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
 
     if (frameInfo != nullptr) {
+        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
         frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
                 frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
         frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
                 gpuCompleteTime, frameInfo->get(FrameInfoIndex::CommandSubmissionCompleted));
-        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
         instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
                                            surfaceControlId);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 387bae1..5e66972 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -3,5 +3,7 @@
 hughchen@google.com
 timhypeng@google.com
 robertluo@google.com
+changbetty@google.com
+songferngwang@google.com
 
 # Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index b416738..39b4b8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -78,6 +78,9 @@
      * Config the MobileStatusTracker to start or stop monitoring platform signals.
      */
     public void setListening(boolean listening) {
+        if (mListening == listening) {
+            return;
+        }
         mListening = listening;
         if (listening) {
             mPhone.registerTelephonyCallback(mReceiverHandler::post, mTelephonyCallback);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ff4c748..182f49c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -240,11 +240,9 @@
     plugins: ["dagger2-compiler"],
 }
 
-// Opt-in config for optimizing the SystemUI target using R8.
-// Enabled via `export SYSTEMUI_OPTIMIZE_JAVA=true`, or explicitly in Make via
-// the `SOONG_CONFIG_ANDROID_SYSTEMUI_OPTIMIZE_JAVA` variable.
-// TODO(b/203472868): Enable optimizations by default after stabilizing and
-// building out retrace infrastructure.
+// Opt-out config for optimizing the SystemUI target using R8.
+// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
+// `SYSTEMUI_OPTIMIZE_JAVA := false`.
 soong_config_module_type {
     name: "systemui_optimized_java_defaults",
     module_type: "java_defaults",
diff --git a/packages/SystemUI/res/drawable/ic_present_to_all.xml b/packages/SystemUI/res/drawable/ic_present_to_all.xml
new file mode 100644
index 0000000..d6c9bbe
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_present_to_all.xml
@@ -0,0 +1,25 @@
+ <!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+     android:width="24dp"
+     android:height="24dp"
+     android:viewportWidth="24"
+     android:viewportHeight="24"
+     android:tint="?attr/colorControlNormal">
+     <path
+         android:fillColor="@android:color/white"
+         android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v14c0,1.11 0.89,2 2,2h18c1.11,0 2,-0.89 2,-2L23,5c0,-1.11 -0.89,-2 -2,-2zM21,19.02L3,19.02L3,4.98h18v14.04zM8,12l4,-4 4,4 -1.41,1.41L13,11.83L13,16h-2v-4.17l-1.59,1.59L8,12z"/>
+ </vector>
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
new file mode 100644
index 0000000..4ad6849
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.internal.widget.ResolverDrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    androidprv:maxCollapsedHeight="0dp"
+    androidprv:maxCollapsedHeightSmall="56dp"
+    androidprv:maxWidth="@*android:dimen/chooser_width"
+    android:id="@*android:id/contentPanel">
+
+    <LinearLayout
+        android:id="@*android:id/chooser_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        androidprv:layout_alwaysShow="true"
+        android:gravity="center"
+        android:elevation="0dp"
+        android:background="@*android:drawable/bottomsheet_background">
+
+        <ImageView
+            android:id="@*android:id/icon"
+            android:layout_width="@dimen/media_projection_app_selector_icon_size"
+            android:layout_height="@dimen/media_projection_app_selector_icon_size"
+            android:layout_marginTop="@*android:dimen/chooser_edge_margin_normal"
+            android:layout_marginBottom="@*android:dimen/chooser_edge_margin_normal"
+            android:importantForAccessibility="no"
+            android:tint="?android:attr/textColorPrimary"/>
+
+        <TextView android:id="@*android:id/title"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:gravity="center"
+            android:paddingBottom="@*android:dimen/chooser_view_spacing"
+            android:paddingLeft="24dp"
+            android:paddingRight="24dp"/>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@*android:id/content_preview_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
+
+    <TabHost
+        android:id="@*android:id/profile_tabhost"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:background="?android:attr/colorBackground">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TabWidget
+                android:id="@*android:id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone">
+            </TabWidget>
+            <FrameLayout
+                android:id="@*android:id/tabcontent"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <com.android.internal.app.ResolverViewPager
+                    android:id="@*android:id/profile_pager"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+            </FrameLayout>
+        </LinearLayout>
+    </TabHost>
+
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e3be365..e5e369e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1440,6 +1440,8 @@
 
     <dimen name="fgs_manager_list_top_spacing">12dp</dimen>
 
+    <dimen name="media_projection_app_selector_icon_size">32dp</dimen>
+
     <!-- Dream overlay related dimensions -->
     <dimen name="dream_overlay_status_bar_height">60dp</dimen>
     <dimen name="dream_overlay_status_bar_margin">40dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b33cea7..df6486a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1644,8 +1644,9 @@
     <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] -->
     <string name="tile_unavailable">Unavailable</string>
 
-    <!-- The tile in quick settings is disabled by a device administration policy [CHAR LIMIT=32] -->
-    <string name="tile_disabled">Disabled</string>
+    <!-- Accessibility text for the click action on a tile that is disabled by policy. This will
+         be used following "Double-tap to..." [CHAR LIMIT=NONE] -->
+    <string name="accessibility_tile_disabled_by_policy_action_description">learn more</string>
 
     <!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
     <string name="nav_bar">Navigation bar</string>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
new file mode 100644
index 0000000..2e391c7
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.graphics.Color
+import android.view.View
+import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.*
+
+/**
+ * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
+ */
+class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+
+    private val colorsRule = MaterialYouColorsRule()
+    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+    private val screenshotRule =
+        ScreenshotTestRule(
+            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+        )
+    private val delegateRule =
+        RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
+    private val matcher = UnitTestBitmapMatcher
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return delegateRule.apply(base, description)
+    }
+
+    /**
+     * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
+     * the context of [emulationSpec].
+     */
+    fun screenshotTest(goldenIdentifier: String, view: View) {
+        view.removeElevationRecursively()
+
+        ScreenshotRuleAsserter.Builder(screenshotRule)
+            .setScreenshotProvider { view.toBitmap() }
+            .withMatcher(matcher)
+            .build()
+            .assertGoldenImage(goldenIdentifier)
+    }
+
+    /**
+     * Compare the content of the [activity] with the golden image identified by [goldenIdentifier]
+     * in the context of [emulationSpec].
+     */
+    fun activityScreenshotTest(
+        goldenIdentifier: String,
+        activity: Activity,
+    ) {
+        val rootView = activity.window.decorView
+
+        // Hide system bars, remove insets, focus and make sure device-specific cutouts
+        // don't affect screenshots
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            val window = activity.window
+            window.setDecorFitsSystemWindows(false)
+            WindowInsetsControllerCompat(window, rootView).apply {
+                hide(WindowInsetsCompat.Type.systemBars())
+                systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+            }
+
+            window.statusBarColor = Color.TRANSPARENT
+            window.navigationBarColor = Color.TRANSPARENT
+            window.attributes =
+                window.attributes.apply {
+                    layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+                }
+
+            rootView.removeInsetsRecursively()
+            activity.currentFocus?.clearFocus()
+        }
+
+        screenshotTest(goldenIdentifier, rootView)
+    }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
new file mode 100644
index 0000000..98e9aaf
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.content.Intent
+import androidx.core.app.AppComponentFactory
+
+class TestAppComponentFactory : AppComponentFactory() {
+
+    init {
+        instance = this
+    }
+
+    private val overrides: MutableMap<String, () -> Activity> = hashMapOf()
+
+    fun clearOverrides() {
+        overrides.clear()
+    }
+
+    fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) {
+        overrides[activity.name] = provider
+    }
+
+    override fun instantiateActivityCompat(
+        cl: ClassLoader,
+        className: String,
+        intent: Intent?
+    ): Activity {
+        return overrides
+            .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) }
+            .invoke()
+    }
+
+    companion object {
+
+        private var instance: TestAppComponentFactory? = null
+
+        fun getInstance(): TestAppComponentFactory =
+            instance
+                ?: error(
+                    "TestAppComponentFactory is not initialized, " +
+                        "did you specify it in the manifest?"
+                )
+    }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
new file mode 100644
index 0000000..b84d26a
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.util.children
+import android.view.WindowInsets
+
+/**
+ * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to
+ * disable it for any view in the hierarchy.
+ */
+fun View.removeElevationRecursively() {
+    this.elevation = 0f
+    (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively)
+}
+
+/**
+ * Different devices could have different insets (e.g. different height of the navigation bar or
+ * taskbar). This method dispatches empty insets to the whole view hierarchy and removes
+ * the original listener, so the views won't receive real insets.
+ */
+fun View.removeInsetsRecursively() {
+    this.dispatchApplyWindowInsets(WindowInsets.CONSUMED)
+    this.setOnApplyWindowInsetsListener(null)
+    (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively)
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index c609e6f..cdedc64 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -1,10 +1,12 @@
 package com.android.systemui.testing.screenshot
 
+import android.annotation.WorkerThread
 import android.app.Activity
 import android.content.Context
 import android.content.ContextWrapper
 import android.graphics.Bitmap
 import android.graphics.Canvas
+import android.graphics.HardwareRenderer
 import android.graphics.Rect
 import android.os.Build
 import android.os.Handler
@@ -19,8 +21,13 @@
 import androidx.concurrent.futures.ResolvableFuture
 import androidx.test.annotation.ExperimentalTestApi
 import androidx.test.core.internal.os.HandlerExecutor
+import androidx.test.espresso.Espresso
 import androidx.test.platform.graphics.HardwareRendererCompat
+import com.google.common.util.concurrent.FutureCallback
+import com.google.common.util.concurrent.Futures
 import com.google.common.util.concurrent.ListenableFuture
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.runBlocking
 
 /*
  * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
@@ -62,6 +69,47 @@
 }
 
 /**
+ * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of
+ * [captureToBitmap].
+ */
+@WorkerThread
+@ExperimentalTestApi
+@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+fun View.toBitmap(window: Window? = null): Bitmap {
+    if (Looper.getMainLooper() == Looper.myLooper()) {
+        error("toBitmap() can't be called from the main thread")
+    }
+
+    if (!HardwareRenderer.isDrawingEnabled()) {
+        error("Hardware rendering is not enabled")
+    }
+
+    // Make sure we are idle.
+    Espresso.onIdle()
+
+    val mainExecutor = context.mainExecutor
+    return runBlocking {
+        suspendCoroutine { continuation ->
+            Futures.addCallback(
+                captureToBitmap(window),
+                object : FutureCallback<Bitmap> {
+                    override fun onSuccess(result: Bitmap) {
+                        continuation.resumeWith(Result.success(result))
+                    }
+
+                    override fun onFailure(t: Throwable) {
+                        continuation.resumeWith(Result.failure(t))
+                    }
+                },
+                // We know that we are not on the main thread, so we can block the current
+                // thread and wait for the result in the main thread.
+                mainExecutor,
+            )
+        }
+    }
+}
+
+/**
  * Trigger a redraw of the given view.
  *
  * Should only be called on UI thread.
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 47e2d2c..0b0595f 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -19,21 +19,13 @@
 import android.app.Activity
 import android.app.Dialog
 import android.graphics.Bitmap
-import android.graphics.HardwareRenderer
-import android.os.Looper
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.Window
 import androidx.activity.ComponentActivity
-import androidx.test.espresso.Espresso
 import androidx.test.ext.junit.rules.ActivityScenarioRule
-import com.google.common.util.concurrent.FutureCallback
-import com.google.common.util.concurrent.Futures
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
@@ -89,6 +81,8 @@
             // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
             // it for any view in the hierarchy.
             window.decorView.removeElevationRecursively()
+
+            activity.currentFocus?.clearFocus()
         }
 
         // We call onActivity again because it will make sure that our Activity is done measuring,
@@ -150,53 +144,11 @@
         }
     }
 
-    private fun View.removeElevationRecursively() {
-        this.elevation = 0f
-
-        if (this is ViewGroup) {
-            repeat(childCount) { i -> getChildAt(i).removeElevationRecursively() }
-        }
-    }
-
     private fun Dialog.toBitmap(): Bitmap {
         val window = window
         return window.decorView.toBitmap(window)
     }
 
-    private fun View.toBitmap(window: Window? = null): Bitmap {
-        if (Looper.getMainLooper() == Looper.myLooper()) {
-            error("toBitmap() can't be called from the main thread")
-        }
-
-        if (!HardwareRenderer.isDrawingEnabled()) {
-            error("Hardware rendering is not enabled")
-        }
-
-        // Make sure we are idle.
-        Espresso.onIdle()
-
-        val mainExecutor = context.mainExecutor
-        return runBlocking {
-            suspendCoroutine { continuation ->
-                Futures.addCallback(
-                    captureToBitmap(window),
-                    object : FutureCallback<Bitmap> {
-                        override fun onSuccess(result: Bitmap?) {
-                            continuation.resumeWith(Result.success(result!!))
-                        }
-
-                        override fun onFailure(t: Throwable) {
-                            continuation.resumeWith(Result.failure(t))
-                        }
-                    },
-                    // We know that we are not on the main thread, so we can block the current
-                    // thread and wait for the result in the main thread.
-                    mainExecutor,
-                )
-            }
-        }
-    }
-
     enum class Mode(val layoutParams: LayoutParams) {
         WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
         MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 0ea1965..919b71b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -52,6 +52,7 @@
     val becauseCannotSkipBouncer: Boolean,
     val biometricSettingEnabledForUser: Boolean,
     val bouncerFullyShown: Boolean,
+    val bouncerIsOrWillShow: Boolean,
     val faceAuthenticated: Boolean,
     val faceDisabled: Boolean,
     val goingToSleep: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 167d8af..cb88f32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2494,7 +2494,7 @@
         // on bouncer if both fp and fingerprint are enrolled.
         final boolean awakeKeyguardExcludingBouncerShowing = mKeyguardIsVisible
                 && mDeviceInteractive && !mGoingToSleep
-                && !statusBarShadeLocked && !mBouncerFullyShown;
+                && !statusBarShadeLocked && !mBouncerIsOrWillBeShowing;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
         final boolean isLockDown =
@@ -2563,6 +2563,7 @@
                     becauseCannotSkipBouncer,
                     biometricEnabledForUser,
                     mBouncerFullyShown,
+                    mBouncerIsOrWillBeShowing,
                     faceAuthenticated,
                     faceDisabledForUser,
                     mGoingToSleep,
@@ -3131,7 +3132,7 @@
                     cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
                 }
             }
-            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
         }
 
         if (wasBouncerFullyShown != mBouncerFullyShown) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a0ecd22..318529b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -42,7 +42,6 @@
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.log.dagger.LogModule;
-import com.android.systemui.lowlightclock.LowLightClockController;
 import com.android.systemui.media.dagger.MediaProjectionModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -94,7 +93,6 @@
 import com.android.systemui.wallet.dagger.WalletModule;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.dagger.DynamicOverride;
 
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -246,21 +244,6 @@
                 sysuiMainExecutor));
     }
 
-    @BindsOptionalOf
-    @DynamicOverride
-    abstract LowLightClockController optionalLowLightClockController();
-
-    @SysUISingleton
-    @Provides
-    static Optional<LowLightClockController> provideLowLightClockController(
-            @DynamicOverride Optional<LowLightClockController> optionalController) {
-        if (optionalController.isPresent() && optionalController.get().isLowLightClockEnabled()) {
-            return optionalController;
-        } else {
-            return Optional.empty();
-        }
-    }
-
     @Binds
     abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockController.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockController.java
deleted file mode 100644
index 0b15f4f..0000000
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockController.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.lowlightclock;
-
-import android.view.ViewGroup;
-
-/**
- * A controller responsible for attaching and showing an optional low-light clock while dozing.
- */
-public interface LowLightClockController {
-    /**
-     * Returns {@code true} if the low-light clock is enabled.
-     */
-    boolean isLowLightClockEnabled();
-
-    /**
-     * Attach the low light-clock to the given parent {@link ViewGroup}.
-     * @param parent The parent {@link ViewGroup} to which the low-light clock view should be
-     *               attached.
-     */
-    void attachLowLightClockView(ViewGroup parent);
-
-    /**
-     * Show or hide the low-light clock.
-     * @param show Whether to show the low-light clock.
-     * @return {@code true} if the low-light clock was shown.
-     */
-    boolean showLowLightClock(boolean show);
-
-    /**
-     * An opportunity to perform burn-in prevention.
-     */
-    void dozeTimeTick();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 53abd99..0f1ee31 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -23,35 +23,47 @@
 import android.os.Bundle
 import android.os.IBinder
 import android.os.ResultReceiver
-import android.view.View
+import android.os.UserHandle
+import android.widget.ImageView
 import com.android.internal.app.ChooserActivity
+import com.android.internal.app.ResolverListController
 import com.android.internal.app.chooser.NotSelectableTargetInfo
 import com.android.internal.app.chooser.TargetInfo
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.util.AsyncActivityLauncher
-import com.android.systemui.R;
-import javax.inject.Inject
+import com.android.systemui.R
+import com.android.internal.R as AndroidR
 
-class MediaProjectionAppSelectorActivity @Inject constructor(
-    private val activityLauncher: AsyncActivityLauncher
+class MediaProjectionAppSelectorActivity constructor(
+    private val activityLauncher: AsyncActivityLauncher,
+    /** This is used to override the dependency in a screenshot test */
+    @VisibleForTesting
+    private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? = null
 ) : ChooserActivity() {
 
+    override fun getLayoutResource() =
+        R.layout.media_projection_app_selector
+
     public override fun onCreate(bundle: Bundle?) {
         val queryIntent = Intent(Intent.ACTION_MAIN)
             .addCategory(Intent.CATEGORY_LAUNCHER)
         intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
 
-        // TODO(b/235465652) Use resource lexeme
-        intent.putExtra(Intent.EXTRA_TITLE, "Record or cast an app")
+        // TODO(b/240939253): update copies
+        val title = getString(R.string.media_projection_dialog_service_title)
+        intent.putExtra(Intent.EXTRA_TITLE, title)
 
         super.onCreate(bundle)
 
-        // TODO(b/235465652) we should update VisD of the title and add an icon
-        findViewById<View>(R.id.title)?.visibility = View.VISIBLE
+        requireViewById<ImageView>(AndroidR.id.icon).setImageResource(R.drawable.ic_present_to_all)
     }
 
     override fun appliedThemeResId(): Int =
         R.style.Theme_SystemUI_MediaProjectionAppSelector
 
+    override fun createListController(userHandle: UserHandle): ResolverListController =
+        listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
+
     override fun startSelected(which: Int, always: Boolean, filtered: Boolean) {
         val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter
         val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
index e33a1b9..9696998 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
@@ -18,8 +18,10 @@
 
 import android.app.Activity
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.util.AsyncActivityLauncher
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 
@@ -29,7 +31,17 @@
     @Binds
     @IntoMap
     @ClassKey(MediaProjectionAppSelectorActivity::class)
-    abstract fun provideMediaProjectionAppSelectorActivity(
+    abstract fun bindMediaProjectionAppSelectorActivity(
         activity: MediaProjectionAppSelectorActivity): Activity
 
+    companion object {
+        @Provides
+        fun provideMediaProjectionAppSelectorActivity(
+            activityLauncher: AsyncActivityLauncher
+        ): MediaProjectionAppSelectorActivity {
+            return MediaProjectionAppSelectorActivity(
+                activityLauncher
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index be6982a..5842665 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -165,7 +165,7 @@
             iv.clearColorFilter();
         }
         if (state.state != mState) {
-            int color = getColor(state.state);
+            int color = getColor(state);
             mState = state.state;
             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
                 animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
@@ -183,7 +183,7 @@
         }
     }
 
-    protected int getColor(int state) {
+    protected int getColor(QSTile.State state) {
         return getIconColorForState(getContext(), state);
     }
 
@@ -239,19 +239,18 @@
     /**
      * Color to tint the tile icon based on state
      */
-    public static int getIconColorForState(Context context, int state) {
-        switch (state) {
-            case Tile.STATE_UNAVAILABLE:
-                return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA,
-                        Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary));
-            case Tile.STATE_INACTIVE:
-                return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
-            case Tile.STATE_ACTIVE:
-                return Utils.getColorAttrDefaultColor(context,
-                        com.android.internal.R.attr.textColorOnAccent);
-            default:
-                Log.e("QSIconView", "Invalid state " + state);
-                return 0;
+    private static int getIconColorForState(Context context, QSTile.State state) {
+        if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
+            return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA,
+                    Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary));
+        } else if (state.state == Tile.STATE_INACTIVE) {
+            return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
+        } else if (state.state == Tile.STATE_ACTIVE) {
+            return Utils.getColorAttrDefaultColor(context,
+                    com.android.internal.R.attr.textColorOnAccent);
+        } else {
+            Log.e("QSIconView", "Invalid state " + state);
+            return 0;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 2731d64..163ee2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -35,6 +35,7 @@
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.Button
 import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.Switch
@@ -144,6 +145,7 @@
         superSetVisibility = { super.setVisibility(it) },
         superSetTransitionVisibility = { super.setTransitionVisibility(it) },
     )
+    private var lastDisabledByPolicy = false
 
     private val locInScreen = IntArray(2)
 
@@ -376,8 +378,22 @@
         super.onInitializeAccessibilityNodeInfo(info)
         // Clear selected state so it is not announce by talkback.
         info.isSelected = false
+        if (lastDisabledByPolicy) {
+            info.addAction(
+                    AccessibilityNodeInfo.AccessibilityAction(
+                            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+                            resources.getString(
+                                R.string.accessibility_tile_disabled_by_policy_action_description
+                            )
+                    )
+            )
+        }
         if (!TextUtils.isEmpty(accessibilityClass)) {
-            info.className = accessibilityClass
+            info.className = if (lastDisabledByPolicy) {
+                Button::class.java.name
+            } else {
+                accessibilityClass
+            }
             if (Switch::class.java.name == accessibilityClass) {
                 val label = resources.getString(
                         if (tileState) R.string.switch_bar_on else R.string.switch_bar_off)
@@ -430,6 +446,10 @@
                 state.secondaryLabel = stateText
             }
         }
+        if (state.disabledByPolicy && state.state != Tile.STATE_UNAVAILABLE) {
+            stateDescription.append(", ")
+            stateDescription.append(getUnavailableText(state.spec))
+        }
         if (!TextUtils.isEmpty(state.stateDescription)) {
             stateDescription.append(", ")
             stateDescription.append(state.stateDescription)
@@ -470,38 +490,38 @@
         }
 
         // Colors
-        if (state.state != lastState) {
+        if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
             singleAnimator.cancel()
             if (allowAnimations) {
                 singleAnimator.setValues(
                         colorValuesHolder(
                                 BACKGROUND_NAME,
                                 paintColor,
-                                getBackgroundColorForState(state.state)
+                                getBackgroundColorForState(state.state, state.disabledByPolicy)
                         ),
                         colorValuesHolder(
                                 LABEL_NAME,
                                 label.currentTextColor,
-                                getLabelColorForState(state.state)
+                                getLabelColorForState(state.state, state.disabledByPolicy)
                         ),
                         colorValuesHolder(
                                 SECONDARY_LABEL_NAME,
                                 secondaryLabel.currentTextColor,
-                                getSecondaryLabelColorForState(state.state)
+                                getSecondaryLabelColorForState(state.state, state.disabledByPolicy)
                         ),
                         colorValuesHolder(
                                 CHEVRON_NAME,
                                 chevronView.imageTintList?.defaultColor ?: 0,
-                                getChevronColorForState(state.state)
+                                getChevronColorForState(state.state, state.disabledByPolicy)
                         )
                     )
                 singleAnimator.start()
             } else {
                 setAllColors(
-                    getBackgroundColorForState(state.state),
-                    getLabelColorForState(state.state),
-                    getSecondaryLabelColorForState(state.state),
-                    getChevronColorForState(state.state)
+                    getBackgroundColorForState(state.state, state.disabledByPolicy),
+                    getLabelColorForState(state.state, state.disabledByPolicy),
+                    getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
+                    getChevronColorForState(state.state, state.disabledByPolicy)
                 )
             }
         }
@@ -512,6 +532,7 @@
         label.isEnabled = !state.disabledByPolicy
 
         lastState = state.state
+        lastDisabledByPolicy = state.disabledByPolicy
     }
 
     private fun setAllColors(
@@ -559,13 +580,14 @@
         }
     }
 
-    private fun getStateText(state: QSTile.State): String {
-        if (state.disabledByPolicy) {
-            return context.getString(R.string.tile_disabled)
-        }
+    private fun getUnavailableText(spec: String?): String {
+        val arrayResId = SubtitleArrayMapping.getSubtitleId(spec)
+        return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE]
+    }
 
+    private fun getStateText(state: QSTile.State): String {
         return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) {
-            var arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
+            val arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
             val array = resources.getStringArray(arrayResId)
             array[state.state]
         } else {
@@ -587,11 +609,11 @@
         return locInScreen.get(1) >= -height
     }
 
-    private fun getBackgroundColorForState(state: Int): Int {
-        return when (state) {
-            Tile.STATE_ACTIVE -> colorActive
-            Tile.STATE_INACTIVE -> colorInactive
-            Tile.STATE_UNAVAILABLE -> colorUnavailable
+    private fun getBackgroundColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
+        return when {
+            state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorUnavailable
+            state == Tile.STATE_ACTIVE -> colorActive
+            state == Tile.STATE_INACTIVE -> colorInactive
             else -> {
                 Log.e(TAG, "Invalid state $state")
                 0
@@ -599,11 +621,11 @@
         }
     }
 
-    private fun getLabelColorForState(state: Int): Int {
-        return when (state) {
-            Tile.STATE_ACTIVE -> colorLabelActive
-            Tile.STATE_INACTIVE -> colorLabelInactive
-            Tile.STATE_UNAVAILABLE -> colorLabelUnavailable
+    private fun getLabelColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
+        return when {
+            state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorLabelUnavailable
+            state == Tile.STATE_ACTIVE -> colorLabelActive
+            state == Tile.STATE_INACTIVE -> colorLabelInactive
             else -> {
                 Log.e(TAG, "Invalid state $state")
                 0
@@ -611,11 +633,11 @@
         }
     }
 
-    private fun getSecondaryLabelColorForState(state: Int): Int {
-        return when (state) {
-            Tile.STATE_ACTIVE -> colorSecondaryLabelActive
-            Tile.STATE_INACTIVE -> colorSecondaryLabelInactive
-            Tile.STATE_UNAVAILABLE -> colorSecondaryLabelUnavailable
+    private fun getSecondaryLabelColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
+        return when {
+            state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorSecondaryLabelUnavailable
+            state == Tile.STATE_ACTIVE -> colorSecondaryLabelActive
+            state == Tile.STATE_INACTIVE -> colorSecondaryLabelInactive
             else -> {
                 Log.e(TAG, "Invalid state $state")
                 0
@@ -623,7 +645,16 @@
         }
     }
 
-    private fun getChevronColorForState(state: Int): Int = getSecondaryLabelColorForState(state)
+    private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int =
+            getSecondaryLabelColorForState(state, disabledByPolicy)
+
+    @VisibleForTesting
+    internal fun getCurrentColors(): List<Int> = listOf(
+            paintColor,
+            label.currentTextColor,
+            secondaryLabel.currentTextColor,
+            chevronView.imageTintList?.defaultColor ?: 0
+    )
 }
 
 @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index fec76b6..e93f605 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -34,7 +34,6 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.lowlightclock.LowLightClockController;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -51,7 +50,6 @@
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 
 import java.io.PrintWriter;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -88,7 +86,6 @@
     private final DockManager mDockManager;
     private final NotificationPanelViewController mNotificationPanelViewController;
     private final PanelExpansionStateManager mPanelExpansionStateManager;
-    private final Optional<LowLightClockController> mLowLightClockController;
 
     private boolean mIsTrackingBarGesture = false;
 
@@ -106,7 +103,6 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             StatusBarWindowStateController statusBarWindowStateController,
             LockIconViewController lockIconViewController,
-            Optional<LowLightClockController> lowLightClockController,
             CentralSurfaces centralSurfaces,
             NotificationShadeWindowController controller,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -125,7 +121,6 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
-        mLowLightClockController = lowLightClockController;
         mService = centralSurfaces;
         mNotificationShadeWindowController = controller;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
@@ -148,8 +143,6 @@
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
         mGestureDetector = new GestureDetector(mView.getContext(), mPulsingGestureListener);
 
-        mLowLightClockController.ifPresent(controller -> controller.attachLowLightClockView(mView));
-
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
@@ -434,21 +427,6 @@
         mStatusBarViewController = statusBarViewController;
     }
 
-    /**
-     * Tell the controller that dozing has begun or ended.
-     * @param dozing True if dozing has begun.
-     */
-    public void setDozing(boolean dozing) {
-        mLowLightClockController.ifPresent(controller -> controller.showLowLightClock(dozing));
-    }
-
-    /**
-     * Tell the controller to perform burn-in prevention.
-     */
-    public void dozeTimeTick() {
-        mLowLightClockController.ifPresent(LowLightClockController::dozeTimeTick);
-    }
-
     @VisibleForTesting
     void setDragDownHelper(DragDownHelper dragDownHelper) {
         mDragDownHelper = dragDownHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index a57d849b2..25c6dce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -38,6 +38,7 @@
 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;
@@ -64,6 +65,15 @@
 
     /**
      * 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,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
new file mode 100644
index 0000000..a02dd34
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity.ui
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import android.telephony.SubscriptionInfo
+import android.view.ContextThemeWrapper
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.connectivity.NetworkController
+import com.android.systemui.statusbar.connectivity.SignalCallback
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Every subscriptionId can have its own CarrierConfig associated with it, so we have to create our
+ * own [Configuration] and track resources based on the full set of available mcc-mnc combinations.
+ *
+ * (for future reference: b/240555502 is the initiating bug for this)
+ */
+@SysUISingleton
+class MobileContextProvider
+@Inject
+constructor(
+    networkController: NetworkController,
+    dumpManager: DumpManager,
+    private val demoModeController: DemoModeController,
+) : Dumpable, DemoMode {
+    private val subscriptions = mutableMapOf<Int, SubscriptionInfo>()
+    private val signalCallback =
+        object : SignalCallback {
+            override fun setSubs(subs: List<SubscriptionInfo>) {
+                subscriptions.clear()
+                subs.forEach { info -> subscriptions[info.subscriptionId] = info }
+            }
+        }
+
+    // These should always be null when not in demo mode
+    private var demoMcc: Int? = null
+    private var demoMnc: Int? = null
+
+    init {
+        networkController.addCallback(signalCallback)
+        dumpManager.registerDumpable(this)
+        demoModeController.addCallback(this)
+    }
+
+    /**
+     * @return a context with the MCC/MNC [Configuration] values corresponding to this
+     * subscriptionId
+     */
+    fun getMobileContextForSub(subId: Int, context: Context): Context {
+        if (demoModeController.isInDemoMode) {
+            return createMobileContextForDemoMode(context)
+        }
+
+        // Fail back to the given context if no sub exists
+        val info = subscriptions[subId] ?: return context
+
+        return createCarrierConfigContext(context, info.mcc, info.mnc)
+    }
+
+    /** For Demo mode (for now), just apply the same MCC/MNC override for all subIds */
+    private fun createMobileContextForDemoMode(context: Context): Context {
+        return createCarrierConfigContext(context, demoMcc ?: 0, demoMnc ?: 0)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println(
+            "Subscriptions below will be inflated with a configuration context with " +
+                "MCC/MNC overrides"
+        )
+        subscriptions.forEach { (subId, info) ->
+            pw.println("  Subscription with subId($subId) with MCC/MNC(${info.mcc}/${info.mnc})")
+        }
+        pw.println("  MCC override: ${demoMcc ?: "(none)"}")
+        pw.println("  MNC override: ${demoMnc ?: "(none)"}")
+    }
+
+    override fun demoCommands(): List<String> {
+        return listOf(COMMAND_NETWORK)
+    }
+
+    override fun onDemoModeFinished() {
+        demoMcc = null
+        demoMnc = null
+    }
+
+    override fun dispatchDemoCommand(command: String, args: Bundle) {
+        val mccmnc = args.getString("mccmnc") ?: return
+        // Only length 5/6 strings are valid
+        if (!(mccmnc.length == 5 || mccmnc.length == 6)) {
+            return
+        }
+
+        // MCC is always the first 3 digits, and mnc is the last 2 or 3
+        demoMcc = mccmnc.subSequence(0, 3).toString().toInt()
+        demoMnc = mccmnc.subSequence(3, mccmnc.length).toString().toInt()
+    }
+
+    companion object {
+        /**
+         * Creates a context based on this [SubscriptionInfo]'s MCC/MNC values, allowing the overlay
+         * system to properly load different carrier's iconography
+         */
+        private fun createCarrierConfigContext(context: Context, mcc: Int, mnc: Int): Context {
+            // Copy the existing configuration
+            val c = Configuration(context.resources.configuration)
+            c.mcc = mcc
+            c.mnc = mnc
+
+            return ContextThemeWrapper(context, context.theme).also { ctx ->
+                ctx.applyOverrideConfiguration(c)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
index a10c745..0bcd3e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
@@ -26,10 +26,7 @@
 class PipelineDumper(pw: PrintWriter) {
     private val ipw = pw.asIndenting()
 
-    fun print(a: Any?) = ipw.print(a)
     fun println(a: Any?) = ipw.println(a)
-    fun withIncreasedIndent(b: () -> Unit) = ipw.withIncreasedIndent(b)
-    fun withIncreasedIndent(r: Runnable) = ipw.withIncreasedIndent(r)
 
     fun dump(label: String, value: Any?) {
         ipw.print("$label: ")
@@ -37,24 +34,26 @@
     }
 
     private fun dump(value: Any?) = when (value) {
-        null, is String, is Int -> println(value)
+        null, is String, is Int -> ipw.println(value)
         is Collection<*> -> dumpCollection(value)
         else -> {
-            println(value.fullPipelineName)
-            withIncreasedIndent { (value as? PipelineDumpable)?.dumpPipeline(this) }
+            ipw.println(value.fullPipelineName)
+            (value as? PipelineDumpable)?.let {
+                ipw.withIncreasedIndent { it.dumpPipeline(this) }
+            }
         }
     }
 
     private fun dumpCollection(values: Collection<Any?>) {
-        println(values.size)
-        withIncreasedIndent { values.forEach { dump(it) } }
+        ipw.println(values.size)
+        ipw.withIncreasedIndent { values.forEach { dump(it) } }
     }
 }
 
 private val Any.bareClassName: String get() {
     val className = javaClass.name
-    val packageName = javaClass.`package`.name
-    return className.substring(packageName.length + 1)
+    val packagePrefixLength = javaClass.`package`?.name?.length?.plus(1) ?: 0
+    return className.substring(packagePrefixLength)
 }
 
 private val Any.barePipelineName: String? get() = when (this) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index 7a37846..3061522 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -78,10 +78,10 @@
     }
 
     override fun dumpPipeline(d: PipelineDumper) = with(d) {
-        dump("ViewRenderer", viewRenderer)
-        dump("OnAfterRenderListListeners", onAfterRenderListListeners)
-        dump("OnAfterRenderGroupListeners", onAfterRenderGroupListeners)
-        dump("OnAfterRenderEntryListeners", onAfterRenderEntryListeners)
+        dump("viewRenderer", viewRenderer)
+        dump("onAfterRenderListListeners", onAfterRenderListListeners)
+        dump("onAfterRenderGroupListeners", onAfterRenderGroupListeners)
+        dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners)
     }
 
     private fun dispatchOnAfterRenderList(
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 deb6150..1169d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -16,6 +16,8 @@
 
 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;
@@ -29,13 +31,13 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.R;
 import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.flags.FeatureFlags;
 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.StatusBarWifiView;
 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.phone.StatusBarSignalPolicy.WifiIconState;
 
@@ -49,7 +51,6 @@
     private final LinearLayout mStatusIcons;
     private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
     private final int mIconSize;
-    private final FeatureFlags mFeatureFlags;
 
     private StatusBarWifiView mWifiView;
     private boolean mDemoMode;
@@ -57,14 +58,12 @@
 
     public DemoStatusIcons(
             LinearLayout statusIcons,
-            int iconSize,
-            FeatureFlags featureFlags
+            int iconSize
     ) {
         super(statusIcons.getContext());
         mStatusIcons = statusIcons;
         mIconSize = iconSize;
         mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
-        mFeatureFlags = featureFlags;
 
         if (statusIcons instanceof StatusIconContainer) {
             setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -253,9 +252,13 @@
         }
     }
 
-    public void addMobileView(MobileIconState state) {
+    /**
+     * Add a new mobile icon view
+     */
+    public void addMobileView(MobileIconState state, Context mobileContext) {
         Log.d(TAG, "addMobileView: ");
-        StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, state.slot);
+        StatusBarMobileView view = StatusBarMobileView
+                .fromContext(mobileContext, state.slot);
 
         view.applyMobileState(state);
         view.setStaticDrawableColor(mColor);
@@ -265,19 +268,24 @@
         addView(view, getChildCount(), createLayoutParams());
     }
 
-    public void updateMobileState(MobileIconState state) {
-        Log.d(TAG, "updateMobileState: ");
-        // If the view for this subId exists already, use it
+    /**
+     * 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) {
-                view.applyMobileState(state);
-                return;
+                removeView(view);
             }
         }
 
-        // Else we have to add it
-        addMobileView(state);
+        // Add the replacement or new icon
+        addMobileView(state, mobileContext);
     }
 
     public void onRemoveIcon(StatusIconDisplayable view) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 80c3e6c..ddff7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -226,7 +226,6 @@
         }
 
         mStatusBarStateController.setIsDozing(dozing);
-        mNotificationShadeWindowViewController.setDozing(dozing);
         if (mFoldAodAnimationController != null) {
             mFoldAodAnimationController.setIsDozing(dozing);
         }
@@ -310,7 +309,6 @@
     public void dozeTimeTick() {
         mNotificationPanel.dozeTimeTick();
         mAuthController.dozeTimeTick();
-        mNotificationShadeWindowViewController.dozeTimeTick();
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
             ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
         }
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 8273d57..bd99713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -36,7 +36,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.BaseStatusBarWifiView;
@@ -44,6 +43,7 @@
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
 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.phone.StatusBarSignalPolicy.WifiIconState;
@@ -70,6 +70,7 @@
     void addIconGroup(IconManager iconManager);
     /** */
     void removeIconGroup(IconManager iconManager);
+
     /** Refresh the state of an IconManager by recreating the views */
     void refreshIconGroup(IconManager iconManager);
     /** */
@@ -82,21 +83,25 @@
     void setSignalIcon(String slot, WifiIconState state);
     /** */
     void setMobileIcons(String slot, List<MobileIconState> states);
+
     /**
      * Display the no calling & SMS icons.
      */
     void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
+
     /**
      * Display the no calling & SMS icons.
      */
     void setNoCallingIcons(String slot, List<CallIndicatorIconState> states);
+
     public void setIconVisibility(String slot, boolean b);
 
     /**
      * Sets the live region mode for the icon
-     * @see android.view.View#setAccessibilityLiveRegion(int)
-     * @param slot Icon slot to set region for
+     *
+     * @param slot                    Icon slot to set region for
      * @param accessibilityLiveRegion live region mode for the icon
+     * @see android.view.View#setAccessibilityLiveRegion(int)
      */
     void setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion);
 
@@ -115,8 +120,8 @@
     static ArraySet<String> getIconHideList(Context context, String hideListStr) {
         ArraySet<String> ret = new ArraySet<>();
         String[] hideList = hideListStr == null
-            ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
-            : hideListStr.split(",");
+                ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
+                : hideListStr.split(",");
         for (String slot : hideList) {
             if (!TextUtils.isEmpty(slot)) {
                 ret.add(slot);
@@ -134,11 +139,14 @@
 
         public DarkIconManager(
                 LinearLayout linearLayout,
-                FeatureFlags featureFlags,
                 StatusBarPipelineFlags statusBarPipelineFlags,
                 Provider<WifiViewModel> wifiViewModelProvider,
+                MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
-            super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
+            super(linearLayout,
+                    statusBarPipelineFlags,
+                    wifiViewModelProvider,
+                    mobileContextProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
                     R.dimen.status_bar_icon_padding);
             mDarkIconDispatcher = darkIconDispatcher;
@@ -195,41 +203,49 @@
 
         @SysUISingleton
         public static class Factory {
-            private final FeatureFlags mFeatureFlags;
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final Provider<WifiViewModel> mWifiViewModelProvider;
+            private final MobileContextProvider mMobileContextProvider;
             private final DarkIconDispatcher mDarkIconDispatcher;
 
             @Inject
             public Factory(
-                    FeatureFlags featureFlags,
                     StatusBarPipelineFlags statusBarPipelineFlags,
                     Provider<WifiViewModel> wifiViewModelProvider,
+                    MobileContextProvider mobileContextProvider,
                     DarkIconDispatcher darkIconDispatcher) {
-                mFeatureFlags = featureFlags;
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiViewModelProvider = wifiViewModelProvider;
+                mMobileContextProvider = mobileContextProvider;
                 mDarkIconDispatcher = darkIconDispatcher;
             }
 
             public DarkIconManager create(LinearLayout group) {
                 return new DarkIconManager(
-                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider,
+                        group,
+                        mStatusBarPipelineFlags,
+                        mWifiViewModelProvider,
+                        mMobileContextProvider,
                         mDarkIconDispatcher);
             }
         }
     }
 
-    /** */
+    /**
+     *
+     */
     class TintedIconManager extends IconManager {
         private int mColor;
 
         public TintedIconManager(
                 ViewGroup group,
-                FeatureFlags featureFlags,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider) {
-            super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
+                Provider<WifiViewModel> wifiViewModelProvider,
+                MobileContextProvider mobileContextProvider) {
+            super(group,
+                    statusBarPipelineFlags,
+                    wifiViewModelProvider,
+                    mobileContextProvider);
         }
 
         @Override
@@ -261,23 +277,26 @@
 
         @SysUISingleton
         public static class Factory {
-            private final FeatureFlags mFeatureFlags;
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final Provider<WifiViewModel> mWifiViewModelProvider;
+            private final MobileContextProvider mMobileContextProvider;
 
             @Inject
             public Factory(
-                    FeatureFlags featureFlags,
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    Provider<WifiViewModel> wifiViewModelProvider) {
-                mFeatureFlags = featureFlags;
+                    Provider<WifiViewModel> wifiViewModelProvider,
+                    MobileContextProvider mobileContextProvider) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiViewModelProvider = wifiViewModelProvider;
+                mMobileContextProvider = mobileContextProvider;
             }
 
             public TintedIconManager create(ViewGroup group) {
                 return new TintedIconManager(
-                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider);
+                        group,
+                        mStatusBarPipelineFlags,
+                        mWifiViewModelProvider,
+                        mMobileContextProvider);
             }
         }
     }
@@ -287,9 +306,9 @@
      */
     class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
-        private final FeatureFlags mFeatureFlags;
         private final StatusBarPipelineFlags mStatusBarPipelineFlags;
         private final Provider<WifiViewModel> mWifiViewModelProvider;
+        private final MobileContextProvider mMobileContextProvider;
         protected final Context mContext;
         protected final int mIconSize;
         // Whether or not these icons show up in dumpsys
@@ -305,13 +324,13 @@
 
         public IconManager(
                 ViewGroup group,
-                FeatureFlags featureFlags,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider) {
+                Provider<WifiViewModel> wifiViewModelProvider,
+                MobileContextProvider mobileContextProvider) {
             mGroup = group;
-            mFeatureFlags = featureFlags;
             mStatusBarPipelineFlags = statusBarPipelineFlags;
             mWifiViewModelProvider = wifiViewModelProvider;
+            mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
@@ -403,12 +422,15 @@
 
         @VisibleForTesting
         protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
-            StatusBarMobileView view = onCreateStatusBarMobileView(slot);
+            // Use the `subId` field as a key to query for the correct context
+            StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
             view.applyMobileState(state);
             mGroup.addView(view, index, onCreateLayoutParams());
 
             if (mIsInDemoMode) {
-                mDemoStatusIcons.addMobileView(state);
+                Context mobileContext = mMobileContextProvider
+                        .getMobileContextForSub(state.subId, mContext);
+                mDemoStatusIcons.addMobileView(state, mobileContext);
             }
             return view;
         }
@@ -427,8 +449,10 @@
                     mContext, slot, mWifiViewModelProvider.get());
         }
 
-        private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
-            StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
+        private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
+            Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
+            StatusBarMobileView view = StatusBarMobileView
+                    .fromContext(mobileContext, slot);
             return view;
         }
 
@@ -516,7 +540,9 @@
             }
 
             if (mIsInDemoMode) {
-                mDemoStatusIcons.updateMobileState(state);
+                Context mobileContext = mMobileContextProvider
+                        .getMobileContextForSub(state.subId, mContext);
+                mDemoStatusIcons.updateMobileState(state, mobileContext);
             }
         }
 
@@ -553,7 +579,7 @@
         }
 
         protected DemoStatusIcons createDemoStatusIcons() {
-            return new DemoStatusIcons((LinearLayout) mGroup, mIconSize, mFeatureFlags);
+            return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index e910f72..619e50b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -58,7 +59,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSIconViewImpl;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.MessageRouter;
@@ -304,7 +304,7 @@
         MemoryIconDrawable(Context context) {
             baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
             dp = context.getResources().getDisplayMetrics().density;
-            paint.setColor(QSIconViewImpl.getIconColorForState(context, STATE_ACTIVE));
+            paint.setColor(Color.WHITE);
         }
 
         public void setRss(long rss) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index bb455da..0bf038d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -86,6 +86,7 @@
     becauseCannotSkipBouncer = false,
     biometricSettingEnabledForUser = false,
     bouncerFullyShown = false,
+    bouncerIsOrWillShow = false,
     onlyFaceEnrolled = false,
     faceAuthenticated = false,
     faceDisabled = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c281965..f5b9503 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1511,7 +1511,7 @@
                 anyBoolean());
         CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue();
 
-        bouncerFullyVisible();
+        bouncerWillBeVisibleSoon();
         mTestableLooper.processAllMessages();
 
         assertThat(cancelSignal.isCanceled()).isTrue();
@@ -1669,6 +1669,11 @@
         setKeyguardBouncerVisibility(true);
     }
 
+    private void bouncerWillBeVisibleSoon() {
+        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, false);
+        mTestableLooper.processAllMessages();
+    }
+
     private void setKeyguardBouncerVisibility(boolean isVisible) {
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
         mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 23e5168..2c76be6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -97,7 +97,7 @@
         ImageView iv = mock(ImageView.class);
         State s = new State();
         s.state = Tile.STATE_ACTIVE;
-        int desiredColor = mIconView.getColor(s.state);
+        int desiredColor = mIconView.getColor(s);
         when(iv.isShown()).thenReturn(true);
 
         mIconView.setIcon(iv, s, true);
@@ -109,7 +109,7 @@
         ImageView iv = mock(ImageView.class);
         State s = new State();
         s.state = Tile.STATE_ACTIVE;
-        int desiredColor = mIconView.getColor(s.state);
+        int desiredColor = mIconView.getColor(s);
         Icon i = mock(Icon.class);
         s.icon = i;
         when(i.toString()).thenReturn("MOCK ICON");
@@ -124,6 +124,18 @@
         assertFalse(mIconView.toString().contains("lastIcon"));
     }
 
+    @Test
+    public void testIconColorDisabledByPolicy_sameAsUnavailable() {
+        State s1 = new State();
+        s1.state = Tile.STATE_INACTIVE;
+        s1.disabledByPolicy = true;
+
+        State s2 = new State();
+        s2.state = Tile.STATE_UNAVAILABLE;
+
+        assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
+    }
+
     private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
         return new Drawable.ConstantState() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 9fdc2fd..d3ec1dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -280,6 +280,88 @@
         assertThat(info.collectionItemInfo).isNull()
     }
 
+    @Test
+    fun testDisabledByPolicyInactive_usesUnavailableColors() {
+        val stateDisabledByPolicy = QSTile.State()
+        stateDisabledByPolicy.state = Tile.STATE_INACTIVE
+        stateDisabledByPolicy.disabledByPolicy = true
+
+        val stateUnavailable = QSTile.State()
+        stateUnavailable.state = Tile.STATE_UNAVAILABLE
+
+        tileView.changeState(stateDisabledByPolicy)
+        val colorsDisabledByPolicy = tileView.getCurrentColors()
+
+        tileView.changeState(stateUnavailable)
+        val colorsUnavailable = tileView.getCurrentColors()
+
+        assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable)
+    }
+
+    @Test
+    fun testDisabledByPolicyActive_usesUnavailableColors() {
+        val stateDisabledByPolicy = QSTile.State()
+        stateDisabledByPolicy.state = Tile.STATE_ACTIVE
+        stateDisabledByPolicy.disabledByPolicy = true
+
+        val stateUnavailable = QSTile.State()
+        stateUnavailable.state = Tile.STATE_UNAVAILABLE
+
+        tileView.changeState(stateDisabledByPolicy)
+        val colorsDisabledByPolicy = tileView.getCurrentColors()
+
+        tileView.changeState(stateUnavailable)
+        val colorsUnavailable = tileView.getCurrentColors()
+
+        assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable)
+    }
+
+    @Test
+    fun testDisabledByPolicy_secondaryLabelText() {
+        val testA11yLabel = "TEST_LABEL"
+        context.orCreateTestableResources
+                .addOverride(
+                        R.string.accessibility_tile_disabled_by_policy_action_description,
+                        testA11yLabel
+                )
+
+        val stateDisabledByPolicy = QSTile.State()
+        stateDisabledByPolicy.state = Tile.STATE_INACTIVE
+        stateDisabledByPolicy.disabledByPolicy = true
+
+        tileView.changeState(stateDisabledByPolicy)
+
+        val info = AccessibilityNodeInfo(tileView)
+        tileView.onInitializeAccessibilityNodeInfo(info)
+        assertThat(
+                info.actionList.find {
+                        it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id
+                }?.label
+        ).isEqualTo(testA11yLabel)
+    }
+
+    @Test
+    fun testDisabledByPolicy_unavailableInStateDescription() {
+        val state = QSTile.BooleanState()
+        val spec = "internet"
+        state.spec = spec
+        state.disabledByPolicy = true
+        state.state = Tile.STATE_INACTIVE
+
+        val unavailableString = "${spec}_unavailable"
+        val offString = "${spec}_off"
+        val onString = "${spec}_on"
+
+        context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf(
+                unavailableString,
+                offString,
+                onString
+        ))
+
+        tileView.changeState(state)
+        assertThat(tileView.stateDescription?.contains(unavailableString)).isTrue()
+    }
+
     class FakeTileView(
         context: Context,
         icon: QSIconView,
@@ -289,4 +371,4 @@
             handleStateChanged(state)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 6d059b1..43fc8983 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.lowlightclock.LowLightClockController
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -39,12 +38,10 @@
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.google.common.truth.Truth.assertThat
-import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
 import org.mockito.Mock
 import org.mockito.Mockito.anyFloat
 import org.mockito.Mockito.never
@@ -87,8 +84,6 @@
     @Mock
     private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
     @Mock
-    private lateinit var lowLightClockController: LowLightClockController
-    @Mock
     private lateinit var pulsingGestureListener: PulsingGestureListener
 
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -114,7 +109,6 @@
             statusBarKeyguardViewManager,
             statusBarWindowStateController,
             lockIconViewController,
-            Optional.of(lowLightClockController),
             centralSurfaces,
             notificationShadeWindowController,
             keyguardUnlockAnimationController,
@@ -252,31 +246,6 @@
         verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
         assertThat(returnVal).isTrue()
     }
-
-    @Test
-    fun testLowLightClockAttachedWhenExpandedStatusBarSetup() {
-        verify(lowLightClockController).attachLowLightClockView(ArgumentMatchers.any())
-    }
-
-    @Test
-    fun testLowLightClockShownWhenDozing() {
-        underTest.setDozing(true)
-        verify(lowLightClockController).showLowLightClock(true)
-    }
-
-    @Test
-    fun testLowLightClockDozeTimeTickCalled() {
-        underTest.dozeTimeTick()
-        verify(lowLightClockController).dozeTimeTick()
-    }
-
-    @Test
-    fun testLowLightClockHiddenWhenNotDozing() {
-        underTest.setDozing(true)
-        verify(lowLightClockController).showLowLightClock(true)
-        underTest.setDozing(false)
-        verify(lowLightClockController).showLowLightClock(false)
-    }
 }
 
 private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 89a2518..001bfee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -38,7 +38,6 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.lowlightclock.LowLightClockController;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -61,8 +60,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -86,7 +83,6 @@
     @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Mock private LockIconViewController mLockIconViewController;
-    @Mock private LowLightClockController mLowLightClockController;
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private AmbientState mAmbientState;
     @Mock private PulsingGestureListener mPulsingGestureListener;
@@ -121,7 +117,6 @@
                 mStatusBarKeyguardViewManager,
                 mStatusBarWindowStateController,
                 mLockIconViewController,
-                Optional.of(mLowLightClockController),
                 mCentralSurfaces,
                 mNotificationShadeWindowController,
                 mKeyguardUnlockAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
new file mode 100644
index 0000000..0fdda62
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity.ui
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.connectivity.NetworkController
+import com.android.systemui.statusbar.connectivity.SignalCallback
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileContextProviderTest : SysuiTestCase() {
+    @Mock private lateinit var networkController: NetworkController
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var demoModeController: DemoModeController
+
+    private lateinit var provider: MobileContextProvider
+    private lateinit var signalCallback: SignalCallback
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        provider =
+            MobileContextProvider(
+                networkController,
+                dumpManager,
+                demoModeController,
+            )
+
+        signalCallback = withArgCaptor { verify(networkController).addCallback(capture()) }
+    }
+
+    @Test
+    fun test_oneSubscription_contextHasMccMnc() {
+        // GIVEN there is one SubscriptionInfo
+        signalCallback.setSubs(listOf(SUB_1))
+
+        // WHEN we ask for a mobile context
+        val ctx = provider.getMobileContextForSub(SUB_1_ID, context)
+
+        // THEN the configuration of that context reflect this subscription's MCC/MNC override
+        val config = ctx.resources.configuration
+        assertThat(config.mcc).isEqualTo(SUB_1_MCC)
+        assertThat(config.mnc).isEqualTo(SUB_1_MNC)
+    }
+
+    @Test
+    fun test_twoSubscriptions_eachContextReflectsMccMnc() {
+        // GIVEN there are two SubscriptionInfos
+        signalCallback.setSubs(listOf(SUB_1, SUB_2))
+
+        // WHEN we ask for a mobile context for each sub
+        val ctx1 = provider.getMobileContextForSub(SUB_1_ID, context)
+        val ctx2 = provider.getMobileContextForSub(SUB_2_ID, context)
+
+        // THEN the configuration of each context reflect this subscription's MCC/MNC override
+        val config1 = ctx1.resources.configuration
+        assertThat(config1.mcc).isEqualTo(SUB_1_MCC)
+        assertThat(config1.mnc).isEqualTo(SUB_1_MNC)
+
+        val config2 = ctx2.resources.configuration
+        assertThat(config2.mcc).isEqualTo(SUB_2_MCC)
+        assertThat(config2.mnc).isEqualTo(SUB_2_MNC)
+    }
+
+    @Test
+    fun test_requestingContextForNonexistentSubscription_returnsGivenContext() {
+        // GIVEN no SubscriptionInfos
+        signalCallback.setSubs(listOf())
+
+        // WHEN we ask for a mobile context for an unknown subscription
+        val ctx = provider.getMobileContextForSub(SUB_1_ID, context)
+
+        // THEN we get the original context back
+        assertThat(ctx).isEqualTo(context)
+    }
+
+    private val SUB_1_ID = 1
+    private val SUB_1_MCC = 123
+    private val SUB_1_MNC = 456
+    private val SUB_1 =
+        mock<SubscriptionInfo>().also {
+            whenever(it.subscriptionId).thenReturn(SUB_1_ID)
+            whenever(it.mcc).thenReturn(SUB_1_MCC)
+            whenever(it.mnc).thenReturn(SUB_1_MNC)
+        }
+
+    private val SUB_2_ID = 2
+    private val SUB_2_MCC = 666
+    private val SUB_2_MNC = 777
+    private val SUB_2 =
+        mock<SubscriptionInfo>().also {
+            whenever(it.subscriptionId).thenReturn(SUB_2_ID)
+            whenever(it.mcc).thenReturn(SUB_2_MCC)
+            whenever(it.mnc).thenReturn(SUB_2_MNC)
+        }
+}
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 ca98143..de7db74 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
@@ -20,7 +20,10 @@
 
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -30,12 +33,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
 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;
@@ -55,15 +58,19 @@
 @SmallTest
 public class StatusBarIconControllerTest extends LeakCheckedTest {
 
+    private MobileContextProvider mMobileContextProvider = mock(MobileContextProvider.class);
+
     @Before
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+        // For testing, ignore context overrides
+        when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
     }
 
     @Test
     public void testSetCalledOnAdd_IconManager() {
         LinearLayout layout = new LinearLayout(mContext);
-        TestIconManager manager = new TestIconManager(layout);
+        TestIconManager manager = new TestIconManager(layout, mMobileContextProvider);
         testCallOnAdd_forManager(manager);
     }
 
@@ -72,9 +79,9 @@
         LinearLayout layout = new LinearLayout(mContext);
         TestDarkIconManager manager = new TestDarkIconManager(
                 layout,
-                mock(FeatureFlags.class),
                 mock(StatusBarPipelineFlags.class),
                 () -> mock(WifiViewModel.class),
+                mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
     }
@@ -114,11 +121,14 @@
 
         TestDarkIconManager(
                 LinearLayout group,
-                FeatureFlags featureFlags,
                 StatusBarPipelineFlags statusBarPipelineFlags,
                 Provider<WifiViewModel> wifiViewModelProvider,
+                MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
-            super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider,
+            super(group,
+                    statusBarPipelineFlags,
+                    wifiViewModelProvider,
+                    contextProvider,
                     darkIconDispatcher);
         }
 
@@ -153,11 +163,11 @@
     }
 
     private static class TestIconManager extends IconManager implements TestableIconManager {
-        TestIconManager(ViewGroup group) {
+        TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
             super(group,
-                    mock(FeatureFlags.class),
                     mock(StatusBarPipelineFlags.class),
-                    () -> mock(WifiViewModel.class));
+                    () -> mock(WifiViewModel.class),
+                    contextProvider);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 07b6843..7cb2fd0 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -28,6 +28,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.IVpnManager;
@@ -771,6 +772,12 @@
 
     @VisibleForTesting
     void onUserStarted(int userId) {
+        UserInfo user = mUserManager.getUserInfo(userId);
+        if (user == null) {
+            logw("Started user doesn't exist. UserId: " + userId);
+            return;
+        }
+
         synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn != null) {
@@ -779,7 +786,8 @@
             }
             userVpn = mDeps.createVpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId);
             mVpns.put(userId, userVpn);
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+
+            if (user.isPrimary() && isLockdownVpnEnabled()) {
                 updateLockdownVpn();
             }
         }
@@ -896,9 +904,15 @@
     }
 
     private void onUserUnlocked(int userId) {
+        UserInfo user = mUserManager.getUserInfo(userId);
+        if (user == null) {
+            logw("Unlocked user doesn't exist. UserId: " + userId);
+            return;
+        }
+
         synchronized (mVpns) {
             // User present may be sent because of an unlock, which might mean an unlocked keystore.
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+            if (user.isPrimary() && isLockdownVpnEnabled()) {
                 updateLockdownVpn();
             } else {
                 startAlwaysOnVpn(userId);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 98238cc..b988b57 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -747,7 +747,7 @@
         return true;
     }
 
-    private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+    private Intent buildVpnManagerEventIntent(@NonNull String category, int errorClass,
             int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
             @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
             @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
@@ -766,6 +766,20 @@
             intent.putExtra(VpnManager.EXTRA_ERROR_CODE, errorCode);
         }
 
+        return intent;
+    }
+
+    private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+            int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
+            @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
+            @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+        final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
+                packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
+        return sendEventToVpnManagerApp(intent, packageName);
+    }
+
+    private boolean sendEventToVpnManagerApp(@NonNull final Intent intent,
+            @NonNull final String packageName) {
         // Allow VpnManager app to temporarily run background services to handle this error.
         // If an app requires anything beyond this grace period, they MUST either declare
         // themselves as a foreground service, or schedule a job/workitem.
@@ -1177,23 +1191,25 @@
                 mContext.unbindService(mConnection);
                 cleanupVpnStateLocked();
             } else if (mVpnRunner != null) {
-                if (!VpnConfig.LEGACY_VPN.equals(mPackage)) {
-                    mAppOpsManager.finishOp(
-                            AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, mOwnerUID, mPackage, null);
-                    // The underlying network, NetworkCapabilities and LinkProperties are not
-                    // necessary to send to VPN app since the purpose of this event is to notify
-                    // VPN app that VPN is deactivated by the user.
-                    // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
-                    //  ConnectivityServiceTest.
-                    if (SdkLevel.isAtLeastT()) {
-                        sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
-                                -1 /* errorClass */, -1 /* errorCode*/, mPackage,
-                                getSessionKeyLocked(), makeVpnProfileStateLocked(),
-                                null /* underlyingNetwork */, null /* nc */, null /* lp */);
-                    }
+                // Build intent first because the sessionKey will be reset after performing
+                // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+                // VpnRunner.exit() to prevent design being changed in the future.
+                // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+                //  ConnectivityServiceTest.
+                final int ownerUid = mOwnerUID;
+                Intent intent = null;
+                if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+                    intent = buildVpnManagerEventIntent(
+                            VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+                            -1 /* errorClass */, -1 /* errorCode*/, mPackage,
+                            getSessionKeyLocked(), makeVpnProfileStateLocked(),
+                            null /* underlyingNetwork */, null /* nc */, null /* lp */);
                 }
                 // cleanupVpnStateLocked() is called from mVpnRunner.exit()
                 mVpnRunner.exit();
+                if (intent != null && isVpnApp(mPackage)) {
+                    notifyVpnManagerVpnStopped(mPackage, ownerUid, intent);
+                }
             }
 
             try {
@@ -2887,6 +2903,9 @@
                 final LinkProperties lp;
 
                 synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
                     mInterface = interfaceName;
                     mConfig.mtu = maxMtu;
                     mConfig.interfaze = mInterface;
@@ -2988,6 +3007,9 @@
 
             try {
                 synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
                     mConfig.underlyingNetworks = new Network[] {network};
                     mNetworkCapabilities =
                             new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3077,7 +3099,12 @@
 
                 // Clear mInterface to prevent Ikev2VpnRunner being cleared when
                 // interfaceRemoved() is called.
-                mInterface = null;
+                synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
+                    mInterface = null;
+                }
                 // Without MOBIKE, we have no way to seamlessly migrate. Close on old
                 // (non-default) network, and start the new one.
                 resetIkeState();
@@ -3262,6 +3289,9 @@
         /** Marks the state as FAILED, and disconnects. */
         private void markFailedAndDisconnect(Exception exception) {
             synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+
                 updateState(DetailedState.FAILED, exception.getMessage());
             }
 
@@ -3300,6 +3330,9 @@
             cancelHandleNetworkLostTimeout();
 
             synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+
                 if (exception instanceof IkeProtocolException) {
                     final IkeProtocolException ikeException = (IkeProtocolException) exception;
 
@@ -3420,6 +3453,9 @@
             Log.d(TAG, "Resetting state for token: " + mCurrentToken);
 
             synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+
                 // Since this method handles non-fatal errors only, set mInterface to null to
                 // prevent the NetworkManagementEventObserver from killing this VPN based on the
                 // interface going down (which we expect).
@@ -4090,7 +4126,32 @@
         // To stop the VPN profile, the caller must be the current prepared package and must be
         // running an Ikev2VpnProfile.
         if (isCurrentIkev2VpnLocked(packageName)) {
-            prepareInternal(VpnConfig.LEGACY_VPN);
+            // Build intent first because the sessionKey will be reset after performing
+            // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+            // VpnRunner.exit() to prevent design being changed in the future.
+            final int ownerUid = mOwnerUID;
+            final Intent intent = buildVpnManagerEventIntent(
+                    VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+                    -1 /* errorClass */, -1 /* errorCode*/, packageName,
+                    getSessionKeyLocked(), makeVpnProfileStateLocked(),
+                    null /* underlyingNetwork */, null /* nc */, null /* lp */);
+
+            mVpnRunner.exit();
+            notifyVpnManagerVpnStopped(packageName, ownerUid, intent);
+        }
+    }
+
+    private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID,
+            Intent intent) {
+        mAppOpsManager.finishOp(
+                AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, ownerUID, packageName, null);
+        // The underlying network, NetworkCapabilities and LinkProperties are not
+        // necessary to send to VPN app since the purpose of this event is to notify
+        // VPN app that VPN is deactivated by the user.
+        // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+        //  ConnectivityServiceTest.
+        if (SdkLevel.isAtLeastT()) {
+            sendEventToVpnManagerApp(intent, packageName);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index c835d2f..25d0752 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -116,10 +116,8 @@
             luxLevels = getLuxLevels(resources.getIntArray(
                     com.android.internal.R.array.config_autoBrightnessLevelsIdle));
         } else {
-            brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
-                    com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
-            luxLevels = getLuxLevels(resources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevels));
+            brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
+            luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
         }
 
         // Display independent, mode independent values
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4f3fd64..12b2f47 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
 import android.os.Environment;
@@ -149,12 +150,22 @@
  *      </quirks>
  *
  *      <autoBrightness>
- *           <brighteningLightDebounceMillis>
+ *          <brighteningLightDebounceMillis>
  *              2000
- *           </brighteningLightDebounceMillis>
+ *          </brighteningLightDebounceMillis>
  *          <darkeningLightDebounceMillis>
  *              1000
  *          </darkeningLightDebounceMillis>
+ *          <displayBrightnessMapping>
+ *              <displayBrightnessPoint>
+ *                  <lux>50</lux>
+ *                  <nits>45.32</nits>
+ *              </displayBrightnessPoint>
+ *              <displayBrightnessPoint>
+ *                  <lux>80</lux>
+ *                  <nits>75.43</nits>
+ *              </displayBrightnessPoint>
+ *          </displayBrightnessMapping>
  *      </autoBrightness>
  *
  *      <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -268,6 +279,34 @@
     // for the corresponding values above
     private float[] mBrightness;
 
+
+    /**
+     * Array of desired screen brightness in nits corresponding to the lux values
+     * in the mBrightnessLevelsLux array. The display brightness is defined as the
+     * measured brightness of an all-white image. The brightness values must be non-negative and
+     * non-decreasing. This must be overridden in platform specific overlays
+     */
+    private float[] mBrightnessLevelsNits;
+
+    /**
+     * Array of light sensor lux values to define our levels for auto backlight
+     * brightness support.
+
+     * The N + 1 entries of this array define N control points defined in mBrightnessLevelsNits,
+     * with first value always being 0 lux
+
+     * The control points must be strictly increasing.  Each control point
+     * corresponds to an entry in the brightness backlight values arrays.
+     * For example, if lux == level[1] (second element of the levels array)
+     * then the brightness will be determined by value[0] (first element
+     * of the brightness values array).
+     *
+     * Spline interpolation is used to determine the auto-brightness
+     * backlight values for lux levels between these control points.
+     *
+     */
+    private float[] mBrightnessLevelsLux;
+
     private float mBacklightMinimum = Float.NaN;
     private float mBacklightMaximum = Float.NaN;
     private float mBrightnessDefault = Float.NaN;
@@ -661,6 +700,20 @@
         return mAutoBrightnessBrighteningLightDebounce;
     }
 
+    /**
+     * @return Auto brightness brightening ambient lux levels
+     */
+    public float[] getAutoBrightnessBrighteningLevelsLux() {
+        return mBrightnessLevelsLux;
+    }
+
+    /**
+     * @return Auto brightness brightening nits levels
+     */
+    public float[] getAutoBrightnessBrighteningLevelsNits() {
+        return mBrightnessLevelsNits;
+    }
+
     @Override
     public String toString() {
         return "DisplayDeviceConfig{"
@@ -703,6 +756,8 @@
                 + mAutoBrightnessBrighteningLightDebounce
                 + ", mAutoBrightnessDarkeningLightDebounce= "
                 + mAutoBrightnessDarkeningLightDebounce
+                + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
+                + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
                 + "}";
     }
 
@@ -779,6 +834,7 @@
         loadBrightnessRampsFromConfigXml();
         loadAmbientLightSensorFromConfigXml();
         setProxSensorUnspecified();
+        loadAutoBrightnessConfigsFromConfigXml();
         mLoadedFrom = "<config.xml>";
     }
 
@@ -991,6 +1047,7 @@
     private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
         loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
         loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
+        loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
     }
 
     /**
@@ -1023,6 +1080,35 @@
         }
     }
 
+    /**
+     * Loads the auto-brightness display brightness mappings. Internally, this takes care of
+     * loading the value from the display config, and if not present, falls back to config.xml.
+     */
+    private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
+        if (autoBrightnessConfig == null
+                || autoBrightnessConfig.getDisplayBrightnessMapping() == null) {
+            mBrightnessLevelsNits = getFloatArray(mContext.getResources()
+                    .obtainTypedArray(com.android.internal.R.array
+                            .config_autoBrightnessDisplayValuesNits), PowerManager
+                    .BRIGHTNESS_OFF_FLOAT);
+            mBrightnessLevelsLux = getLuxLevels(mContext.getResources()
+                    .getIntArray(com.android.internal.R.array
+                            .config_autoBrightnessLevels));
+        } else {
+            final int size = autoBrightnessConfig.getDisplayBrightnessMapping()
+                    .getDisplayBrightnessPoint().size();
+            mBrightnessLevelsNits = new float[size];
+            // The first control point is implicit and always at 0 lux.
+            mBrightnessLevelsLux = new float[size + 1];
+            for (int i = 0; i < size; i++) {
+                mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
+                        .getDisplayBrightnessPoint().get(i).getNits().floatValue();
+                mBrightnessLevelsLux[i + 1] = autoBrightnessConfig.getDisplayBrightnessMapping()
+                        .getDisplayBrightnessPoint().get(i).getLux().floatValue();
+            }
+        }
+    }
+
     private void loadBrightnessMapFromConfigXml() {
         // Use the config.xml mapping
         final Resources res = mContext.getResources();
@@ -1248,6 +1334,10 @@
                 com.android.internal.R.string.config_displayLightSensorType);
     }
 
+    private void loadAutoBrightnessConfigsFromConfigXml() {
+        loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
+    }
+
     private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
         final SensorDetails sensorDetails = config.getLightSensor();
         if (sensorDetails != null) {
@@ -1390,6 +1480,31 @@
         }
     }
 
+    /**
+     * Extracts a float array from the specified {@link TypedArray}.
+     *
+     * @param array The array to convert.
+     * @return the given array as a float array.
+     */
+    public static float[] getFloatArray(TypedArray array, float defaultValue) {
+        final int n = array.length();
+        float[] vals = new float[n];
+        for (int i = 0; i < n; i++) {
+            vals[i] = array.getFloat(i, defaultValue);
+        }
+        array.recycle();
+        return vals;
+    }
+
+    private static float[] getLuxLevels(int[] lux) {
+        // The first control point is implicit and always at 0 lux.
+        float[] levels = new float[lux.length + 1];
+        for (int i = 0; i < lux.length; i++) {
+            levels[i + 1] = (float) lux[i];
+        }
+        return levels;
+    }
+
     static class SensorData {
         public String type;
         public String name;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d44a3b7..4e0cc61 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4953,7 +4953,16 @@
             }
             enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
 
-            return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule,
+            // If the caller is system, take the package name from the rule's owner rather than
+            // from the caller's package.
+            String rulePkg = pkg;
+            if (isCallingUidSystem()) {
+                if (automaticZenRule.getOwner() != null) {
+                    rulePkg = automaticZenRule.getOwner().getPackageName();
+                }
+            }
+
+            return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
                     "addAutomaticZenRule");
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0c4273f..e9d5426 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5798,6 +5798,11 @@
             final Computer snapshot = snapshotComputer();
             enforceOwnerRights(snapshot, packageName, Binder.getCallingUid());
             mimeTypes = CollectionUtils.emptyIfNull(mimeTypes);
+            for (String mimeType : mimeTypes) {
+                if (mimeType.length() > 255) {
+                    throw new IllegalArgumentException("MIME type length exceeds 255 characters");
+                }
+            }
             final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
             Set<String> existingMimeTypes = packageState.getMimeGroups().get(mimeGroup);
             if (existingMimeTypes == null) {
@@ -5808,6 +5813,10 @@
                     && existingMimeTypes.containsAll(mimeTypes)) {
                 return;
             }
+            if (mimeTypes.size() > 500) {
+                throw new IllegalStateException("Max limit on MIME types for MIME group "
+                        + mimeGroup + " exceeded for package " + packageName);
+            }
 
             ArraySet<String> mimeTypesSet = new ArraySet<>(mimeTypes);
             commitPackageStateMutation(null, packageName, packageStateWrite -> {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b3ba20b..37538db 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1112,15 +1112,21 @@
                             resolvedAttributionSource, skipCurrentFinish);
                 }
 
-                if (next == null || next.getNext() == null) {
-                    return;
-                }
-
                 RegisteredAttribution registered =
                         sRunningAttributionSources.remove(current.getToken());
                 if (registered != null) {
                     registered.unregister();
                 }
+
+                if (next == null || next.getNext() == null) {
+                    if (next != null) {
+                        registered = sRunningAttributionSources.remove(next.getToken());
+                        if (registered != null) {
+                            registered.unregister();
+                        }
+                    }
+                    return;
+                }
                 current = next;
             }
         }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 67d9aec..6c8e9f0 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -1856,6 +1856,9 @@
         for (int i = component.getIntents().size() - 1; i >= 0; i--) {
             IntentFilter filter = component.getIntents().get(i).getIntentFilter();
             for (int groupIndex = filter.countMimeGroups() - 1; groupIndex >= 0; groupIndex--) {
+                if (mimeGroups != null && mimeGroups.size() > 500) {
+                    throw new IllegalStateException("Max limit on number of MIME Groups reached");
+                }
                 mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d572b1b..af22f80 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -357,7 +357,6 @@
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.WindowManagerService.H;
 import com.android.server.wm.utils.InsetUtils;
-import com.android.server.wm.utils.WmDisplayCutout;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -918,7 +917,6 @@
      */
     private final Configuration mTmpConfig = new Configuration();
     private final Rect mTmpBounds = new Rect();
-    private final Rect mTmpOutNonDecorBounds = new Rect();
 
     // Token for targeting this activity for assist purposes.
     final Binder assistToken = new Binder();
@@ -8152,10 +8150,12 @@
         final int orientation = parentBounds.height() >= parentBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // Compute orientation from stable parent bounds (= parent bounds with insets applied)
+        final DisplayInfo di = isFixedRotationTransforming()
+                ? getFixedRotationTransformDisplayInfo()
+                : mDisplayContent.getDisplayInfo();
         final Task task = getTask();
-        task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
-                outStableBounds /* outStableBounds */, parentBounds /* bounds */,
-                mDisplayContent.getDisplayInfo());
+        task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
+                outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
         final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // If orientation does not match the orientation with insets applied, then a
@@ -9718,10 +9718,10 @@
                 final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
                 final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
                 final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
-                final WmDisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation);
-                policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]);
-                mStableInsets[rotation].set(mNonDecorInsets[rotation]);
-                policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation);
+                final DisplayPolicy.DecorInsets.Info decorInfo =
+                        policy.getDecorInsetsInfo(rotation, dw, dh);
+                mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+                mStableInsets[rotation].set(decorInfo.mConfigInsets);
 
                 if (unfilledContainerBounds == null) {
                     continue;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 603a792..720b082 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1080,7 +1080,7 @@
                 * mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
         mInsetsStateController = new InsetsStateController(this);
-        mDisplayFrames = new DisplayFrames(mDisplayId, mInsetsStateController.getRawInsetsState(),
+        mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
                 mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
                 calculateRoundedCornersForRotation(mDisplayInfo.rotation),
                 calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
@@ -1936,11 +1936,11 @@
     private void startFixedRotationTransform(WindowToken token, int rotation) {
         mTmpConfiguration.unset();
         final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
-        final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
+        final DisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
         final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
         final PrivacyIndicatorBounds indicatorBounds =
                 calculatePrivacyIndicatorBoundsForRotation(rotation);
-        final DisplayFrames displayFrames = new DisplayFrames(mDisplayId, new InsetsState(), info,
+        final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
                 cutout, roundedCorners, indicatorBounds);
         token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
     }
@@ -2147,12 +2147,10 @@
         final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
 
         // Update application display metrics.
-        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
-        final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+        final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
         final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
 
-        final Rect appFrame = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
-                wmDisplayCutout);
+        final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
         mDisplayInfo.rotation = rotation;
         mDisplayInfo.logicalWidth = dw;
         mDisplayInfo.logicalHeight = dh;
@@ -2190,9 +2188,10 @@
         return mDisplayInfo;
     }
 
-    WmDisplayCutout calculateDisplayCutoutForRotation(int rotation) {
+    DisplayCutout calculateDisplayCutoutForRotation(int rotation) {
         return mDisplayCutoutCache.getOrCompute(
-                mIsSizeForced ? mBaseDisplayCutout : mInitialDisplayCutout, rotation);
+                mIsSizeForced ? mBaseDisplayCutout : mInitialDisplayCutout, rotation)
+                        .getDisplayCutout();
     }
 
     static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached(
@@ -2263,9 +2262,7 @@
         final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
         outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh);
         outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds());
-
-        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
-        computeScreenAppConfiguration(outConfig, dw, dh, rotation, wmDisplayCutout);
+        computeScreenAppConfiguration(outConfig, dw, dh, rotation);
 
         final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
         displayInfo.rotation = rotation;
@@ -2274,7 +2271,7 @@
         final Rect appBounds = outConfig.windowConfiguration.getAppBounds();
         displayInfo.appWidth = appBounds.width();
         displayInfo.appHeight = appBounds.height();
-        final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+        final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
         displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
         computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh,
                 mDisplayMetrics.density, outConfig);
@@ -2283,21 +2280,17 @@
 
     /** Compute configuration related to application without changing current display. */
     private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
-            int rotation, WmDisplayCutout wmDisplayCutout) {
-        final DisplayFrames displayFrames =
-                mDisplayPolicy.getSimulatedDisplayFrames(rotation, dw, dh, wmDisplayCutout);
-        final Rect appFrame =
-                mDisplayPolicy.getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
+            int rotation) {
+        final DisplayPolicy.DecorInsets.Info info =
+                mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh);
         // AppBounds at the root level should mirror the app screen size.
-        outConfig.windowConfiguration.setAppBounds(appFrame);
+        outConfig.windowConfiguration.setAppBounds(info.mNonDecorFrame);
         outConfig.windowConfiguration.setRotation(rotation);
         outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
 
         final float density = mDisplayMetrics.density;
-        final Point configSize =
-                mDisplayPolicy.getConfigDisplaySizeWithSimulatedFrame(displayFrames);
-        outConfig.screenWidthDp = (int) (configSize.x / density + 0.5f);
-        outConfig.screenHeightDp = (int) (configSize.y / density + 0.5f);
+        outConfig.screenWidthDp = (int) (info.mConfigFrame.width() / density + 0.5f);
+        outConfig.screenHeightDp = (int) (info.mConfigFrame.height() / density + 0.5f);
         outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
         outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
 
@@ -2320,8 +2313,7 @@
         config.windowConfiguration.setWindowingMode(getWindowingMode());
         config.windowConfiguration.setDisplayWindowingMode(getWindowingMode());
 
-        computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation,
-                calculateDisplayCutoutForRotation(getRotation()));
+        computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation);
 
         config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
                 | ((displayInfo.flags & Display.FLAG_ROUND) != 0
@@ -2430,9 +2422,8 @@
 
     private int reduceCompatConfigWidthSize(int curSize, int rotation,
             DisplayMetrics dm, int dw, int dh) {
-        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
-        final Rect nonDecorSize = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
-                wmDisplayCutout);
+        final Rect nonDecorSize =
+                mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
         dm.noncompatWidthPixels = nonDecorSize.width();
         dm.noncompatHeightPixels = nonDecorSize.height();
         float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
@@ -2481,11 +2472,8 @@
     }
 
     private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) {
-        // Get the display cutout at this rotation.
-        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
-
         // Get the app screen size at this rotation.
-        final Rect size = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, wmDisplayCutout);
+        final Rect size = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
 
         // Compute the screen layout size class for this rotation.
         int longSize = size.width();
@@ -2501,19 +2489,21 @@
     }
 
     private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
-        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
-        final Point size = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, wmDisplayCutout);
-        if (size.x < displayInfo.smallestNominalAppWidth) {
-            displayInfo.smallestNominalAppWidth = size.x;
+        final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
+                rotation, dw, dh);
+        final int w = info.mConfigFrame.width();
+        final int h = info.mConfigFrame.height();
+        if (w < displayInfo.smallestNominalAppWidth) {
+            displayInfo.smallestNominalAppWidth = w;
         }
-        if (size.x > displayInfo.largestNominalAppWidth) {
-            displayInfo.largestNominalAppWidth = size.x;
+        if (w > displayInfo.largestNominalAppWidth) {
+            displayInfo.largestNominalAppWidth = w;
         }
-        if (size.y < displayInfo.smallestNominalAppHeight) {
-            displayInfo.smallestNominalAppHeight = size.y;
+        if (h < displayInfo.smallestNominalAppHeight) {
+            displayInfo.smallestNominalAppHeight = h;
         }
-        if (size.y > displayInfo.largestNominalAppHeight) {
-            displayInfo.largestNominalAppHeight = size.y;
+        if (h > displayInfo.largestNominalAppHeight) {
+            displayInfo.largestNominalAppHeight = h;
         }
     }
 
@@ -2773,14 +2763,19 @@
     }
 
     private void updateDisplayFrames(boolean notifyInsetsChange) {
-        if (mDisplayFrames.update(mDisplayInfo,
-                calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
-                calculateRoundedCornersForRotation(mDisplayInfo.rotation),
-                calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation))) {
+        if (updateDisplayFrames(mDisplayFrames, mDisplayInfo.rotation,
+                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
             mInsetsStateController.onDisplayFramesUpdated(notifyInsetsChange);
         }
     }
 
+    boolean updateDisplayFrames(DisplayFrames displayFrames, int rotation, int w, int h) {
+        return displayFrames.update(rotation, w, h,
+                calculateDisplayCutoutForRotation(rotation),
+                calculateRoundedCornersForRotation(rotation),
+                calculatePrivacyIndicatorBoundsForRotation(rotation));
+    }
+
     @Override
     void onDisplayChanged(DisplayContent dc) {
         super.onDisplayChanged(dc);
@@ -2943,6 +2938,9 @@
                         + mBaseDisplayHeight + " on display:" + getDisplayId());
             }
         }
+        if (mDisplayReady) {
+            mDisplayPolicy.mDecorInsets.invalidate();
+        }
     }
 
     /**
@@ -5584,7 +5582,7 @@
             outExclusionUnrestricted.setEmpty();
         }
         final Region unhandled = Region.obtain();
-        unhandled.set(0, 0, mDisplayFrames.mDisplayWidth, mDisplayFrames.mDisplayHeight);
+        unhandled.set(0, 0, mDisplayFrames.mWidth, mDisplayFrames.mHeight);
 
         final Rect leftEdge = mInsetsStateController.getSourceProvider(ITYPE_LEFT_GESTURES)
                 .getSource().getFrame();
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 7ca38b8..33641f7 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -30,8 +30,6 @@
 import android.view.PrivacyIndicatorBounds;
 import android.view.RoundedCorners;
 
-import com.android.server.wm.utils.WmDisplayCutout;
-
 import java.io.PrintWriter;
 
 /**
@@ -39,8 +37,6 @@
  * @hide
  */
 public class DisplayFrames {
-    public final int mDisplayId;
-
     public final InsetsState mInsetsState;
 
     /**
@@ -54,48 +50,45 @@
      */
     public final Rect mDisplayCutoutSafe = new Rect();
 
-    public int mDisplayWidth;
-    public int mDisplayHeight;
+    public int mWidth;
+    public int mHeight;
 
     public int mRotation;
 
-    public DisplayFrames(int displayId, InsetsState insetsState, DisplayInfo info,
-            WmDisplayCutout displayCutout, RoundedCorners roundedCorners,
-            PrivacyIndicatorBounds indicatorBounds) {
-        mDisplayId = displayId;
+    public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout,
+            RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) {
         mInsetsState = insetsState;
-        update(info, displayCutout, roundedCorners, indicatorBounds);
+        update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners,
+                indicatorBounds);
+    }
+
+    DisplayFrames() {
+        mInsetsState = new InsetsState();
     }
 
     /**
-     * This is called when {@link DisplayInfo} or {@link PrivacyIndicatorBounds} is updated.
+     * This is called if the display info may be changed, e.g. rotation, size, insets.
      *
-     * @param info the updated {@link DisplayInfo}.
-     * @param displayCutout the updated {@link DisplayCutout}.
-     * @param roundedCorners the updated {@link RoundedCorners}.
-     * @param indicatorBounds the updated {@link PrivacyIndicatorBounds}.
      * @return {@code true} if anything has been changed; {@code false} otherwise.
      */
-    public boolean update(DisplayInfo info, @NonNull WmDisplayCutout displayCutout,
+    public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout,
             @NonNull RoundedCorners roundedCorners,
             @NonNull PrivacyIndicatorBounds indicatorBounds) {
         final InsetsState state = mInsetsState;
         final Rect safe = mDisplayCutoutSafe;
-        final DisplayCutout cutout = displayCutout.getDisplayCutout();
-        if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight
-                && mRotation == info.rotation
-                && state.getDisplayCutout().equals(cutout)
+        if (mRotation == rotation && mWidth == w && mHeight == h
+                && mInsetsState.getDisplayCutout().equals(displayCutout)
                 && state.getRoundedCorners().equals(roundedCorners)
                 && state.getPrivacyIndicatorBounds().equals(indicatorBounds)) {
             return false;
         }
-        mDisplayWidth = info.logicalWidth;
-        mDisplayHeight = info.logicalHeight;
-        mRotation = info.rotation;
+        mRotation = rotation;
+        mWidth = w;
+        mHeight = h;
         final Rect unrestricted = mUnrestricted;
-        unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
+        unrestricted.set(0, 0, w, h);
         state.setDisplayFrame(unrestricted);
-        state.setDisplayCutout(cutout);
+        state.setDisplayCutout(displayCutout);
         state.setRoundedCorners(roundedCorners);
         state.setPrivacyIndicatorBounds(indicatorBounds);
         state.getDisplayCutoutSafe(safe);
@@ -132,7 +125,6 @@
     }
 
     public void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
-                + " r=" + mRotation);
+        pw.println(prefix + "DisplayFrames w=" + mWidth + " h=" + mHeight + " r=" + mRotation);
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 713c13e..fe7cb19 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -19,19 +19,15 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.Display.TYPE_INTERNAL;
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -107,7 +103,6 @@
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.gui.DropInputMode;
@@ -132,8 +127,6 @@
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.InsetsVisibilities;
-import android.view.PrivacyIndicatorBounds;
-import android.view.RoundedCorners;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
@@ -151,7 +144,6 @@
 import com.android.internal.policy.ForceShowNavBarSettingsObserver;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.internal.util.ScreenshotHelper;
@@ -166,7 +158,6 @@
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
-import com.android.server.wm.utils.WmDisplayCutout;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -242,6 +233,8 @@
 
     private final SystemGesturesPointerEventListener mSystemGestures;
 
+    final DecorInsets mDecorInsets;
+
     private volatile int mLidState = LID_ABSENT;
     private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     private volatile boolean mHdmiPlugged;
@@ -266,7 +259,6 @@
 
     private WindowState mStatusBar = null;
     private volatile WindowState mNotificationShade;
-    private final int[] mStatusBarHeightForRotation = new int[4];
     private WindowState mNavigationBar = null;
     @NavigationBarPosition
     private int mNavigationBarPosition = NAV_BAR_BOTTOM;
@@ -353,7 +345,6 @@
 
     private static final Rect sTmpRect = new Rect();
     private static final Rect sTmpRect2 = new Rect();
-    private static final Rect sTmpLastParentFrame = new Rect();
     private static final Rect sTmpDisplayCutoutSafe = new Rect();
     private static final ClientWindowFrames sTmpClientFrames = new ClientWindowFrames();
 
@@ -391,16 +382,6 @@
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
 
-    // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
-    private static final int[] STABLE_TYPES = new int[]{
-            ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
-            ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
-    };
-    private static final int[] NON_DECOR_TYPES = new int[]{
-            ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
-            ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
-    };
-
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
     private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
@@ -444,6 +425,7 @@
                 : service.mAtmService.mSystemThread
                         .getSystemUiContext(displayContent.getDisplayId());
         mDisplayContent = displayContent;
+        mDecorInsets = new DecorInsets(displayContent);
         mLock = service.getWindowManagerLock();
 
         final int displayId = displayContent.getDisplayId();
@@ -1227,7 +1209,7 @@
                                     Math.max(displayFrames.mDisplayCutoutSafe.left, 0);
                             inOutFrame.left = 0;
                             inOutFrame.top = 0;
-                            inOutFrame.bottom = displayFrames.mDisplayHeight;
+                            inOutFrame.bottom = displayFrames.mHeight;
                             inOutFrame.right = leftSafeInset + mLeftGestureInset;
                         });
                 mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win,
@@ -1237,8 +1219,8 @@
                                             displayFrames.mUnrestricted.right);
                             inOutFrame.left = rightSafeInset - mRightGestureInset;
                             inOutFrame.top = 0;
-                            inOutFrame.bottom = displayFrames.mDisplayHeight;
-                            inOutFrame.right = displayFrames.mDisplayWidth;
+                            inOutFrame.bottom = displayFrames.mHeight;
+                            inOutFrame.right = displayFrames.mWidth;
                         });
                 mDisplayContent.setInsetProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT, win,
                         (displayFrames, windowContainer, inOutFrame) -> {
@@ -1429,11 +1411,6 @@
         return Math.max(statusBarHeight, displayFrames.mDisplayCutoutSafe.top);
     }
 
-    @VisibleForTesting
-    int getStatusBarHeightForRotation(@Surface.Rotation int rotation) {
-        return SystemBarUtils.getStatusBarHeightForRotation(mUiContext, rotation);
-    }
-
     WindowState getStatusBar() {
         return mStatusBar != null ? mStatusBar : mStatusBarAlt;
     }
@@ -1929,25 +1906,11 @@
 
         final Resources res = getCurrentUserResources();
         final int portraitRotation = displayRotation.getPortraitRotation();
-        final int upsideDownRotation = displayRotation.getUpsideDownRotation();
-        final int landscapeRotation = displayRotation.getLandscapeRotation();
-        final int seascapeRotation = displayRotation.getSeascapeRotation();
 
         if (hasStatusBar()) {
-            mStatusBarHeightForRotation[portraitRotation] =
-                    mStatusBarHeightForRotation[upsideDownRotation] =
-                            getStatusBarHeightForRotation(portraitRotation);
-            mStatusBarHeightForRotation[landscapeRotation] =
-                    getStatusBarHeightForRotation(landscapeRotation);
-            mStatusBarHeightForRotation[seascapeRotation] =
-                    getStatusBarHeightForRotation(seascapeRotation);
             mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
                     R.dimen.display_cutout_touchable_region_size);
         } else {
-            mStatusBarHeightForRotation[portraitRotation] =
-                    mStatusBarHeightForRotation[upsideDownRotation] =
-                            mStatusBarHeightForRotation[landscapeRotation] =
-                                    mStatusBarHeightForRotation[seascapeRotation] = 0;
             mDisplayCutoutTouchableRegionSize = 0;
         }
 
@@ -2053,33 +2016,9 @@
     }
 
     /**
-     * Return the display frame available after excluding any screen decorations that could never be
-     * removed in Honeycomb. That is, system bar or button bar.
-     *
-     * @return display frame excluding all non-decor insets.
-     */
-    Rect getNonDecorDisplayFrame(int fullWidth, int fullHeight, int rotation,
-            WmDisplayCutout cutout) {
-        final DisplayFrames displayFrames =
-                getSimulatedDisplayFrames(rotation, fullWidth, fullHeight, cutout);
-        return getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
-    }
-
-    Rect getNonDecorDisplayFrameWithSimulatedFrame(DisplayFrames displayFrames) {
-        final Rect nonDecorInsets =
-                getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect();
-        final Rect displayFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
-        displayFrame.inset(nonDecorInsets);
-        return displayFrame;
-    }
-
-    /**
      * Get the Navigation Bar Frame height. This dimension is the height of the navigation bar that
      * is used for spacing to show additional buttons on the navigation bar (such as the ime
-     * switcher when ime is visible) while {@link #getNavigationBarHeight} is used for the visible
-     * height that we send to the app as content insets that can be smaller.
-     * <p>
-     * In car mode it will return the same height as {@link #getNavigationBarHeight}
+     * switcher when ime is visible).
      *
      * @param rotation specifies rotation to return dimension from
      * @return navigation bar frame height
@@ -2092,26 +2031,6 @@
     }
 
     /**
-     * Return the available screen size that we should report for the
-     * configuration.  This must be no larger than
-     * {@link #getNonDecorDisplayFrame(int, int, int, DisplayCutout)}; it may be smaller
-     * than that to account for more transient decoration like a status bar.
-     */
-    public Point getConfigDisplaySize(int fullWidth, int fullHeight, int rotation,
-            WmDisplayCutout wmDisplayCutout) {
-        final DisplayFrames displayFrames = getSimulatedDisplayFrames(rotation, fullWidth,
-                fullHeight, wmDisplayCutout);
-        return getConfigDisplaySizeWithSimulatedFrame(displayFrames);
-    }
-
-    Point getConfigDisplaySizeWithSimulatedFrame(DisplayFrames displayFrames) {
-        final Insets insets = getInsetsWithInternalTypes(displayFrames, STABLE_TYPES);
-        Rect configFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
-        configFrame.inset(insets);
-        return new Point(configFrame.width(), configFrame.height());
-    }
-
-    /**
      * Return corner radius in pixels that should be used on windows in order to cover the display.
      *
      * The radius is only valid for internal displays, since the corner radius of external displays
@@ -2126,89 +2045,152 @@
         return mShowingDream;
     }
 
-    /**
-     * Calculates the stable insets if we already have the non-decor insets.
-     *
-     * @param inOutInsets The known non-decor insets. It will be modified to stable insets.
-     * @param rotation The current display rotation.
-     */
-    void convertNonDecorInsetsToStableInsets(Rect inOutInsets, int rotation) {
-        inOutInsets.top = Math.max(inOutInsets.top, mStatusBarHeightForRotation[rotation]);
+    /** The latest insets and frames for screen configuration calculation. */
+    static class DecorInsets {
+        static class Info {
+            /**
+             * The insets for the areas that could never be removed, i.e. display cutout and
+             * navigation bar. Note that its meaning is actually "decor insets". The "non" is just
+             * because it is used to calculate {@link #mNonDecorFrame}.
+             */
+            final Rect mNonDecorInsets = new Rect();
+
+            /**
+             * The stable insets that can affect configuration. The sources are usually from
+             * display cutout, navigation bar, and status bar.
+             */
+            final Rect mConfigInsets = new Rect();
+
+            /** The display frame available after excluding {@link #mNonDecorInsets}. */
+            final Rect mNonDecorFrame = new Rect();
+
+            /**
+             * The available (stable) screen size that we should report for the configuration.
+             * This must be no larger than {@link #mNonDecorFrame}; it may be smaller than that
+             * to account for more transient decoration like a status bar.
+             */
+            final Rect mConfigFrame = new Rect();
+
+            private boolean mNeedUpdate = true;
+
+            void update(DisplayContent dc, int rotation, int w, int h) {
+                final DisplayFrames df = new DisplayFrames();
+                dc.updateDisplayFrames(df, rotation, w, h);
+                dc.getDisplayPolicy().simulateLayoutDisplay(df);
+                final InsetsState insetsState = df.mInsetsState;
+                final Rect displayFrame = insetsState.getDisplayFrame();
+                final Insets decor = calculateDecorInsetsWithInternalTypes(insetsState);
+                final Insets statusBar = insetsState.calculateInsets(displayFrame,
+                        Type.statusBars(), true /* ignoreVisibility */);
+                mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
+                mConfigInsets.set(Math.max(statusBar.left, decor.left),
+                        Math.max(statusBar.top, decor.top),
+                        Math.max(statusBar.right, decor.right),
+                        Math.max(statusBar.bottom, decor.bottom));
+                mNonDecorFrame.set(displayFrame);
+                mNonDecorFrame.inset(mNonDecorInsets);
+                mConfigFrame.set(displayFrame);
+                mConfigFrame.inset(mConfigInsets);
+                mNeedUpdate = false;
+            }
+
+            void set(Info other) {
+                mNonDecorInsets.set(other.mNonDecorInsets);
+                mConfigInsets.set(other.mConfigInsets);
+                mNonDecorFrame.set(other.mNonDecorFrame);
+                mConfigFrame.set(other.mConfigFrame);
+                mNeedUpdate = false;
+            }
+
+            @Override
+            public String toString() {
+                return "{nonDecorInsets=" + mNonDecorInsets
+                        + ", configInsets=" + mConfigInsets
+                        + ", nonDecorFrame=" + mNonDecorFrame
+                        + ", configFrame=" + mConfigFrame + '}';
+            }
+        }
+
+        // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
+        static final int[] INTERNAL_DECOR_TYPES;
+        static {
+            final ArraySet<Integer> decorTypes = InsetsState.toInternalType(
+                    Type.displayCutout() | Type.navigationBars());
+            decorTypes.remove(ITYPE_EXTRA_NAVIGATION_BAR);
+            INTERNAL_DECOR_TYPES = new int[decorTypes.size()];
+            for (int i = 0; i < INTERNAL_DECOR_TYPES.length; i++) {
+                INTERNAL_DECOR_TYPES[i] = decorTypes.valueAt(i);
+            }
+        }
+
+        private final DisplayContent mDisplayContent;
+        private final Info[] mInfoForRotation = new Info[4];
+        final Info mTmpInfo = new Info();
+
+        DecorInsets(DisplayContent dc) {
+            mDisplayContent = dc;
+            for (int i = mInfoForRotation.length - 1; i >= 0; i--) {
+                mInfoForRotation[i] = new Info();
+            }
+        }
+
+        Info get(int rotation, int w, int h) {
+            final Info info = mInfoForRotation[rotation];
+            if (info.mNeedUpdate) {
+                info.update(mDisplayContent, rotation, w, h);
+            }
+            return info;
+        }
+
+        /** Called when the screen decor insets providers have changed. */
+        void invalidate() {
+            for (Info info : mInfoForRotation) {
+                info.mNeedUpdate = true;
+            }
+        }
+
+        // TODO (b/235842600): Remove this method once we can treat task bar as navigation bar.
+        private static Insets calculateDecorInsetsWithInternalTypes(InsetsState state) {
+            final Rect frame = state.getDisplayFrame();
+            Insets insets = Insets.NONE;
+            for (int i = INTERNAL_DECOR_TYPES.length - 1; i >= 0; i--) {
+                final InsetsSource source = state.peekSource(INTERNAL_DECOR_TYPES[i]);
+                if (source != null) {
+                    insets = Insets.max(source.calculateInsets(frame, true /* ignoreVisibility */),
+                            insets);
+                }
+            }
+            return insets;
+        }
     }
 
     /**
-     * Calculates the stable insets without running a layout.
-     *
-     * @param displayRotation the current display rotation
-     * @param displayWidth full display width
-     * @param displayHeight full display height
-     * @param displayCutout the current display cutout
-     * @param outInsets the insets to return
+     * If the decor insets changes, the display configuration may be affected. The caller should
+     * call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
      */
-    public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
-            WmDisplayCutout displayCutout, Rect outInsets) {
-        final DisplayFrames displayFrames = getSimulatedDisplayFrames(displayRotation,
-                displayWidth, displayHeight, displayCutout);
-        getStableInsetsWithSimulatedFrame(displayFrames, outInsets);
+    boolean updateDecorInsetsInfoIfNeeded(WindowState win) {
+        if (!win.providesNonDecorInsets()) {
+            return false;
+        }
+        final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
+        final int rotation = displayFrames.mRotation;
+        final int dw = displayFrames.mWidth;
+        final int dh = displayFrames.mHeight;
+        final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
+        newInfo.update(mDisplayContent, rotation, dw, dh);
+        final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
+        if (newInfo.mNonDecorFrame.equals(currentInfo.mNonDecorFrame)) {
+            return false;
+        }
+        mDecorInsets.invalidate();
+        mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+        // If the device is booting, let the boot procedure trigger the new configuration.
+        // Otherwise the display configuration needs to be recomputed now.
+        return mService.mDisplayEnabled;
     }
 
-    void getStableInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
-        // Navigation bar, status bar, and cutout.
-        outInsets.set(getInsetsWithInternalTypes(displayFrames, STABLE_TYPES).toRect());
-    }
-
-    /**
-     * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
-     * bar or button bar. See {@link #getNonDecorDisplayFrame}.
-     *
-     * @param displayRotation the current display rotation
-     * @param fullWidth the width of the display, including all insets
-     * @param fullHeight the height of the display, including all insets
-     * @param cutout the current display cutout
-     * @param outInsets the insets to return
-     */
-    public void getNonDecorInsetsLw(int displayRotation, int fullWidth, int fullHeight,
-            WmDisplayCutout cutout, Rect outInsets) {
-        final DisplayFrames displayFrames =
-                getSimulatedDisplayFrames(displayRotation, fullWidth, fullHeight, cutout);
-        getNonDecorInsetsWithSimulatedFrame(displayFrames, outInsets);
-    }
-
-    void getNonDecorInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
-        outInsets.set(getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect());
-    }
-
-    DisplayFrames getSimulatedDisplayFrames(int displayRotation, int fullWidth,
-            int fullHeight, WmDisplayCutout cutout) {
-        final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
-        info.rotation = displayRotation;
-        info.logicalWidth = fullWidth;
-        info.logicalHeight = fullHeight;
-        info.displayCutout = cutout.getDisplayCutout();
-        final RoundedCorners roundedCorners =
-                mDisplayContent.calculateRoundedCornersForRotation(displayRotation);
-        final PrivacyIndicatorBounds indicatorBounds =
-                mDisplayContent.calculatePrivacyIndicatorBoundsForRotation(displayRotation);
-        final DisplayFrames displayFrames = new DisplayFrames(getDisplayId(), new InsetsState(),
-                info, cutout, roundedCorners, indicatorBounds);
-        simulateLayoutDisplay(displayFrames);
-        return displayFrames;
-    }
-
-    @VisibleForTesting
-    Insets getInsets(DisplayFrames displayFrames, @InsetsType int type) {
-        final InsetsState state = displayFrames.mInsetsState;
-        final Insets insets = state.calculateInsets(state.getDisplayFrame(), type,
-                true /* ignoreVisibility */);
-        return insets;
-    }
-
-    Insets getInsetsWithInternalTypes(DisplayFrames displayFrames,
-            @InternalInsetsType int[] types) {
-        final InsetsState state = displayFrames.mInsetsState;
-        final Insets insets = state.calculateInsetsWithInternalTypes(state.getDisplayFrame(), types,
-                true /* ignoreVisibility */);
-        return insets;
+    DecorInsets.Info getDecorInsetsInfo(int rotation, int w, int h) {
+        return mDecorInsets.get(rotation, w, h);
     }
 
     @NavigationBarPosition
@@ -2851,6 +2833,11 @@
         pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
         pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
         pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
+        pw.print(prefix); pw.println("mDecorInsetsInfo:");
+        for (int rotation = 0; rotation < mDecorInsets.mInfoForRotation.length; rotation++) {
+            final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
+            pw.println(prefixInner + Surface.rotationToString(rotation) + "=" + info);
+        }
         mSystemGestures.dump(pw, prefix);
 
         pw.print(prefix); pw.println("Looper state:");
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ed771c2..ac1fbc3 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -34,6 +34,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
@@ -44,6 +45,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Manages global window inset state in the system represented by {@link InsetsState}.
@@ -86,8 +88,17 @@
         }
     };
 
+    private final Function<Integer, WindowContainerInsetsSourceProvider> mSourceProviderFunc;
+
     InsetsStateController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
+        mSourceProviderFunc = type -> {
+            final InsetsSource source = mState.getSource(type);
+            if (type == ITYPE_IME) {
+                return new ImeInsetsSourceProvider(source, this, mDisplayContent);
+            }
+            return new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
+        };
     }
 
     InsetsState getRawInsetsState() {
@@ -115,15 +126,7 @@
      * @return The provider of a specific type.
      */
     WindowContainerInsetsSourceProvider getSourceProvider(@InternalInsetsType int type) {
-        if (type == ITYPE_IME) {
-            return mProviders.computeIfAbsent(type,
-                    key -> new ImeInsetsSourceProvider(
-                            mState.getSource(key), this, mDisplayContent));
-        } else {
-            return mProviders.computeIfAbsent(type,
-                    key -> new WindowContainerInsetsSourceProvider(mState.getSource(key), this,
-                            mDisplayContent));
-        }
+        return mProviders.computeIfAbsent(type, mSourceProviderFunc);
     }
 
     ImeInsetsSourceProvider getImeSourceProvider() {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 32fa420..036a292 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -98,7 +98,6 @@
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.am.HostingRecord;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.wm.utils.WmDisplayCutout;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -298,10 +297,11 @@
 
     final Point mLastSurfaceSize = new Point();
 
-    private final Rect mTmpInsets = new Rect();
     private final Rect mTmpBounds = new Rect();
     private final Rect mTmpFullBounds = new Rect();
+    /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */
     private final Rect mTmpStableBounds = new Rect();
+    /** For calculating app bounds, i.e. the area without the nav bar and display cutout. */
     private final Rect mTmpNonDecorBounds = new Rect();
 
     //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
@@ -2202,21 +2202,16 @@
             DisplayInfo displayInfo) {
         outNonDecorBounds.set(bounds);
         outStableBounds.set(bounds);
-        final Task rootTask = getRootTaskFragment().asTask();
-        if (rootTask == null || rootTask.mDisplayContent == null) {
+        if (mDisplayContent == null) {
             return;
         }
         mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
 
-        final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
-        final WmDisplayCutout cutout =
-                rootTask.mDisplayContent.calculateDisplayCutoutForRotation(displayInfo.rotation);
-        final DisplayFrames displayFrames = policy.getSimulatedDisplayFrames(displayInfo.rotation,
-                displayInfo.logicalWidth, displayInfo.logicalHeight, cutout);
-        policy.getNonDecorInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
-        intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
-        policy.getStableInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
-        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
+        final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
+                displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
+        intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
+        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 8e389d3..5495b30 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -80,6 +80,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.animation.Animation;
@@ -706,7 +707,11 @@
                 // remove the surfaces yet. If it is currently visible, but not expected-visible,
                 // then doing commitVisibility here would actually be out-of-order and leave the
                 // activity in a bad state.
-                if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
+                // TODO (b/243755838) Create a screen off transition to correct the visible status
+                // of activities.
+                final boolean isScreenOff = ar.mDisplayContent == null
+                        || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF;
+                if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) {
                     final boolean commitVisibility = !checkEnterPipOnFinish(ar);
                     // Avoid commit visibility if entering pip or else we will get a sudden
                     // "flash" / surface going invisible for a split second.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6819833..cc17b5b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -319,7 +319,6 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
-import com.android.server.wm.utils.WmDisplayCutout;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -1857,7 +1856,7 @@
                     + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
 
             if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation())
-                    || win.providesNonDecorInsets()) {
+                    || displayPolicy.updateDecorInsetsInfoIfNeeded(win)) {
                 displayContent.sendNewConfiguration();
             }
 
@@ -2254,7 +2253,7 @@
             Arrays.fill(outActiveControls, null);
         }
         int result = 0;
-        boolean configChanged;
+        boolean configChanged = false;
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
@@ -2321,10 +2320,15 @@
                 flagChanges = win.mAttrs.flags ^ attrs.flags;
                 privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
                 attrChanges = win.mAttrs.copyFrom(attrs);
-                if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
-                        | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) {
+                final boolean layoutChanged =
+                        (attrChanges & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0;
+                if (layoutChanged || (attrChanges
+                        & WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED) != 0) {
                     win.mLayoutNeeded = true;
                 }
+                if (layoutChanged) {
+                    configChanged = displayPolicy.updateDecorInsetsInfoIfNeeded(win);
+                }
                 if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
                         || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
                     win.mActivityRecord.checkKeyguardFlagsChanged();
@@ -2534,7 +2538,7 @@
             }
 
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: updateOrientation");
-            configChanged = displayContent.updateOrientation();
+            configChanged |= displayContent.updateOrientation();
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
             if (toBeDisplayed && win.mIsWallpaper) {
@@ -7177,9 +7181,8 @@
         final DisplayContent dc = mRoot.getDisplayContent(displayId);
         if (dc != null) {
             final DisplayInfo di = dc.getDisplayInfo();
-            final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(di.rotation);
-            dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
-                    cutout, outInsets);
+            outInsets.set(dc.getDisplayPolicy().getDecorInsetsInfo(
+                    di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b601cc5..65db359 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2511,6 +2511,7 @@
                     mWinAnimator.mSurfaceController,
                     Debug.getCallers(5));
 
+        final DisplayContent displayContent = getDisplayContent();
         final long origId = Binder.clearCallingIdentity();
 
         try {
@@ -2565,7 +2566,7 @@
                     // Set up a replacement input channel since the app is now dead.
                     // We need to catch tapping on the dead window to restart the app.
                     openInputChannel(null);
-                    getDisplayContent().getInputMonitor().updateInputWindowsLw(true /*force*/);
+                    displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
                     return;
                 }
 
@@ -2573,7 +2574,7 @@
                 // usually unnoticeable (e.g. covered by rotation animation) and the animation
                 // bounds could be inconsistent, such as depending on when the window applies
                 // its draw transaction with new rotation.
-                final boolean allowExitAnimation = !getDisplayContent().inTransition()
+                final boolean allowExitAnimation = !displayContent.inTransition()
                         // There will be a new window so the exit animation may not be visible or
                         // look weird if its orientation is changed.
                         && !inRelaunchingActivity();
@@ -2623,18 +2624,11 @@
             }
 
             removeImmediately();
-            boolean sentNewConfig = false;
-            if (wasVisible) {
-                // Removing a visible window will effect the computed orientation
-                // So just update orientation if needed.
-                final DisplayContent displayContent = getDisplayContent();
-                if (displayContent.updateOrientation()) {
-                    displayContent.sendNewConfiguration();
-                    sentNewConfig = true;
-                }
-            }
-            if (!sentNewConfig && providesNonDecorInsets()) {
-                getDisplayContent().sendNewConfiguration();
+            // Removing a visible window may affect the display orientation so just update it if
+            // needed. Also recompute configuration if it provides screen decor insets.
+            if ((wasVisible && displayContent.updateOrientation())
+                    || displayContent.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(this)) {
+                displayContent.sendNewConfiguration();
             }
             mWmService.updateFocusedWindowLocked(isFocused()
                             ? UPDATE_FOCUS_REMOVING_FOCUS
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
deleted file mode 100644
index 477625a..0000000
--- a/services/core/xsd/display-device-config/autobrightness.xsd
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-    Copyright (C) 2022 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<xs:schema version="2.0"
-           elementFormDefault="qualified"
-           xmlns:xs="http://www.w3.org/2001/XMLSchema">
-    <xs:complexType name="autoBrightness">
-        <xs:sequence>
-            <!-- Sets the debounce for autoBrightness brightening in millis-->
-            <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
-                        minOccurs="0" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-            <!-- Sets the debounce for autoBrightness darkening in millis-->
-            <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
-                        minOccurs="0" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-        </xs:sequence>
-    </xs:complexType>
-</xs:schema>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index bea5e2c..6b05d8f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,7 +23,6 @@
 <xs:schema version="2.0"
            elementFormDefault="qualified"
            xmlns:xs="http://www.w3.org/2001/XMLSchema">
-    <xs:include schemaLocation="autobrightness.xsd" />
     <xs:element name="displayConfiguration">
         <xs:complexType>
             <xs:sequence>
@@ -343,4 +342,74 @@
             <xs:annotation name="final"/>
         </xs:element>
     </xs:complexType>
+
+    <xs:complexType name="autoBrightness">
+        <xs:sequence>
+            <!-- Sets the debounce for autoBrightness brightening in millis-->
+            <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+                        minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+            <!-- Sets the debounce for autoBrightness darkening in millis-->
+            <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+                        minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+            <!-- Sets the brightness mapping of the desired screen brightness in nits to the
+             corresponding lux for the current display -->
+            <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping"
+                        minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- Represents the brightness mapping of the desired screen brightness in nits to the
+             corresponding lux for the current display -->
+    <xs:complexType name="displayBrightnessMapping">
+        <xs:sequence>
+            <!-- Sets the list of display brightness points, each representing the desired screen
+            brightness in nits to the corresponding lux for the current display
+
+            The N entries of this array define N + 1 control points as follows:
+            (1-based arrays)
+
+            Point 1:            (0, nits[1]):             currentLux <= 0
+            Point 2:     (lux[1], nits[2]):       0 < currentLux <= lux[1]
+            Point 3:     (lux[2], nits[3]):  lux[2] < currentLux <= lux[3]
+            ...
+            Point N+1: (lux[N], nits[N+1]):            lux[N] < currentLux
+
+            The control points must be strictly increasing. Each control point
+            corresponds to an entry in the brightness backlight values arrays.
+            For example, if currentLux == lux[1] (first element of the levels array)
+            then the brightness will be determined by nits[2] (second element
+            of the brightness values array).
+            -->
+            <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
+                        minOccurs="1" maxOccurs="unbounded">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- Represents a point in the display brightness mapping, representing the lux level from the
+    light sensor to the desired screen brightness in nits at this level  -->
+    <xs:complexType name="displayBrightnessPoint">
+        <xs:sequence>
+            <!-- The lux level from the light sensor. This must be a non-negative integer -->
+            <xs:element name="lux" type="xs:nonNegativeInteger"
+                        minOccurs="1" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+
+            <!-- Desired screen brightness in nits corresponding to the suggested lux values.
+             The display brightness is defined as the measured brightness of an all-white image.
+             This must be a non-negative integer -->
+            <xs:element name="nits" type="nonNegativeDecimal"
+                        minOccurs="1" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e9a9269..fb7a920 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -5,8 +5,10 @@
     ctor public AutoBrightness();
     method public final java.math.BigInteger getBrighteningLightDebounceMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
+    method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
+    method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
   }
 
   public class BrightnessThresholds {
@@ -43,6 +45,19 @@
     method public java.util.List<com.android.server.display.config.Density> getDensity();
   }
 
+  public class DisplayBrightnessMapping {
+    ctor public DisplayBrightnessMapping();
+    method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
+  }
+
+  public class DisplayBrightnessPoint {
+    ctor public DisplayBrightnessPoint();
+    method public final java.math.BigInteger getLux();
+    method public final java.math.BigDecimal getNits();
+    method public final void setLux(java.math.BigInteger);
+    method public final void setNits(java.math.BigDecimal);
+  }
+
   public class DisplayConfiguration {
     ctor public DisplayConfiguration();
     method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 617321b..9c615d1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -149,6 +149,12 @@
                 .thenReturn(mockArray);
         when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
                 .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+                .thenReturn(mockArray);
+        when(mMockedResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLevels))
+                .thenReturn(new int[]{});
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 03ea613..66420ad 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -19,16 +19,19 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,22 +55,16 @@
     private Resources mResources;
 
     @Before
-    public void setUp() throws IOException {
+    public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getResources()).thenReturn(mResources);
         mockDeviceConfigs();
-        try {
-            Path tempFile = Files.createTempFile("display_config", ".tmp");
-            Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
-            mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
-            mDisplayDeviceConfig.initFromFile(tempFile.toFile());
-        } catch (IOException e) {
-            throw new IOException("Failed to setup the display device config.", e);
-        }
     }
 
     @Test
-    public void testConfigValues() {
+    public void testConfigValuesFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
         assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
         assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
@@ -88,10 +85,23 @@
         assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
-
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+                float[]{0.0f, 50.0f, 80.0f}, 0.0f);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+                float[]{45.32f, 75.43f}, 0.0f);
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-        // Also add test for the case where optional display configs are null
+    }
+
+    @Test
+    public void testConfigValuesFromConfigResource() {
+        setupDisplayDeviceConfigFromConfigResourceFile();
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+                float[]{2.0f, 200.0f, 600.0f}, 0.0f);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+                float[]{0.0f, 0.0f, 110.0f, 500.0f}, 0.0f);
+        // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
+        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
 
     private String getContent() {
@@ -114,6 +124,16 @@
                 +   "<autoBrightness>\n"
                 +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
                 +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+                +       "<displayBrightnessMapping>\n"
+                +            "<displayBrightnessPoint>\n"
+                +                "<lux>50</lux>\n"
+                +                "<nits>45.32</nits>\n"
+                +            "</displayBrightnessPoint>\n"
+                +            "<displayBrightnessPoint>\n"
+                +                "<lux>80</lux>\n"
+                +                "<nits>75.43</nits>\n"
+                +            "</displayBrightnessPoint>\n"
+                +       "</displayBrightnessMapping>\n"
                 +   "</autoBrightness>\n"
                 +   "<highBrightnessMode enabled=\"true\">\n"
                 +       "<transitionPoint>0.62</transitionPoint>\n"
@@ -185,4 +205,63 @@
         when(mResources.getFloat(com.android.internal.R.dimen
                 .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
     }
+
+    private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+        Path tempFile = Files.createTempFile("display_config", ".tmp");
+        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+        mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+    }
+
+    private void setupDisplayDeviceConfigFromConfigResourceFile() {
+        TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
+        when(mResources.obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessNits))
+                .thenReturn(screenBrightnessNits);
+        TypedArray screenBrightnessBacklight = createFloatTypedArray(new
+                float[]{0.0f, 120.0f, 255.0f});
+        when(mResources.obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessBacklight))
+                .thenReturn(screenBrightnessBacklight);
+        when(mResources.getIntArray(com.android.internal.R.array
+                .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
+
+        when(mResources.getIntArray(com.android.internal.R.array
+                .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
+        when(mResources.getIntArray(com.android.internal.R.array
+                .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
+
+        TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
+                float[]{2.0f, 200.0f, 600.0f});
+        when(mResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+                .thenReturn(screenBrightnessLevelNits);
+        int[] screenBrightnessLevelLux = new int[]{0, 110, 500};
+        when(mResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLevels))
+                .thenReturn(screenBrightnessLevelLux);
+
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
 }
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 e85d7db..a545c83 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7548,6 +7548,43 @@
     }
 
     @Test
+    public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception {
+        mService.isSystemUid = true;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+        // verify that zen mode helper gets passed in a package name of "android"
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+    }
+
+    @Test
+    public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
+        mService.isSystemUid = false;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "another.package");
+
+        // verify that zen mode helper gets passed in the package name from the arg, not the owner
+        verify(mockZenModeHelper).addAutomaticZenRule(
+                eq("another.package"), eq(rule), anyString());
+    }
+
+    @Test
     public void testAreNotificationsEnabledForPackage() throws Exception {
         mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
                 mUid);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index eb61a9c..bbd7695 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -158,7 +158,6 @@
 
 import com.android.internal.R;
 import com.android.server.wm.ActivityRecord.State;
-import com.android.server.wm.utils.WmDisplayCutout;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -552,9 +551,9 @@
         final Rect insets = new Rect();
         final DisplayInfo displayInfo = task.mDisplayContent.getDisplayInfo();
         final DisplayPolicy policy = task.mDisplayContent.getDisplayPolicy();
-        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
-                displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
-        policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
+
+        insets.set(policy.getDecorInsetsInfo(displayInfo.rotation, displayInfo.logicalWidth,
+                displayInfo.logicalHeight).mConfigInsets);
         Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
 
         final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
@@ -594,9 +593,8 @@
         final Rect insets = new Rect();
         final DisplayInfo displayInfo = rootTask.mDisplayContent.getDisplayInfo();
         final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
-        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
-                displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
-        policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
+        insets.set(policy.getDecorInsetsInfo(displayInfo.rotation, displayInfo.logicalWidth,
+                displayInfo.logicalHeight).mConfigInsets);
         Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
 
         final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
@@ -2988,31 +2986,27 @@
     }
 
     @Test
-    public void testCloseToSquareFixedOrientationPortrait() {
+    public void testCloseToSquareFixedOrientation() {
         // create a square display
         final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
                 .setSystemDecorations(true).build();
+        // Add a decor insets provider window.
+        final WindowState navbar = createNavBarWithProvidedInsets(squareDisplay);
+        squareDisplay.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(navbar);
         final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
 
         // create a fixed portrait activity
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+        ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
 
-        // check that both the configuration and app bounds are portrait
+        // The available space could be landscape because of decor insets, but the configuration
+        // should still respect the requested portrait orientation.
         assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
         assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
                 <= activity.getConfiguration().windowConfiguration.getAppBounds().height());
-    }
-
-    @Test
-    public void testCloseToSquareFixedOrientationLandscape() {
-        // create a square display
-        final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
-                .setSystemDecorations(true).build();
-        final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
 
         // create a fixed landscape activity
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+        activity = new ActivityBuilder(mAtm).setTask(task)
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE).build();
 
         // check that both the configuration and app bounds are landscape
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index a001eda..e298d51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -25,13 +25,10 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.util.Pair;
 import android.view.DisplayInfo;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.utils.WmDisplayCutout;
-
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
@@ -49,8 +46,7 @@
 
     @Test
     public void portrait() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_0, false /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -59,8 +55,7 @@
 
     @Test
     public void portrait_withCutout() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_0, true /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT);
@@ -69,8 +64,7 @@
 
     @Test
     public void landscape() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_90, false /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -85,8 +79,7 @@
 
     @Test
     public void landscape_withCutout() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_90, true /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -101,8 +94,7 @@
 
     @Test
     public void seascape() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_270, false /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
@@ -117,8 +109,7 @@
 
     @Test
     public void seascape_withCutout() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_270, true /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
@@ -133,8 +124,7 @@
 
     @Test
     public void upsideDown() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_180, false /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -143,34 +133,32 @@
 
     @Test
     public void upsideDown_withCutout() {
-        final Pair<DisplayInfo, WmDisplayCutout> di =
-                displayInfoForRotation(ROTATION_180, true /* withCutout */);
+        final DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
         verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
         verifyConsistency(di);
     }
 
-    private void verifyStableInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+    private void verifyStableInsets(DisplayInfo di, int left, int top,
             int right, int bottom) {
-        mErrorCollector.checkThat("stableInsets", getStableInsetsLw(diPair.first, diPair.second),
+        mErrorCollector.checkThat("stableInsets", getStableInsets(di),
                 equalTo(new Rect(left, top, right, bottom)));
     }
 
-    private void verifyNonDecorInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+    private void verifyNonDecorInsets(DisplayInfo di, int left, int top,
             int right, int bottom) {
         mErrorCollector.checkThat("nonDecorInsets",
-                getNonDecorInsetsLw(diPair.first, diPair.second), equalTo(new Rect(
-                left, top, right, bottom)));
+                getNonDecorInsets(di), equalTo(new Rect(left, top, right, bottom)));
     }
 
-    private void verifyConsistency(Pair<DisplayInfo, WmDisplayCutout> diPair) {
-        final DisplayInfo di = diPair.first;
-        final WmDisplayCutout cutout = diPair.second;
-        verifyConsistency("configDisplay", di, getStableInsetsLw(di, cutout),
-                getConfigDisplayWidth(di, cutout), getConfigDisplayHeight(di, cutout));
-        verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di, cutout),
-                getNonDecorDisplayWidth(di, cutout), getNonDecorDisplayHeight(di, cutout));
+    private void verifyConsistency(DisplayInfo  di) {
+        final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
+                di.rotation, di.logicalWidth, di.logicalHeight);
+        verifyConsistency("configDisplay", di, info.mConfigInsets,
+                info.mConfigFrame.width(), info.mConfigFrame.height());
+        verifyConsistency("nonDecorDisplay", di, info.mNonDecorInsets,
+                info.mNonDecorFrame.width(), info.mNonDecorFrame.height());
     }
 
     private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width,
@@ -181,42 +169,18 @@
                 equalTo(di.logicalHeight - insets.top - insets.bottom));
     }
 
-    private Rect getStableInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
-        Rect result = new Rect();
-        mDisplayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
-                cutout, result);
-        return result;
+    private Rect getStableInsets(DisplayInfo di) {
+        return mDisplayPolicy.getDecorInsetsInfo(
+                di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets;
     }
 
-    private Rect getNonDecorInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
-        Rect result = new Rect();
-        mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
-                cutout, result);
-        return result;
+    private Rect getNonDecorInsets(DisplayInfo di) {
+        return mDisplayPolicy.getDecorInsetsInfo(
+                di.rotation, di.logicalWidth, di.logicalHeight).mNonDecorInsets;
     }
 
-    private int getNonDecorDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
-        return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
-                di.rotation, cutout).width();
-    }
-
-    private int getNonDecorDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
-        return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
-                di.rotation, cutout).height();
-    }
-
-    private int getConfigDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
-        return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
-                di.rotation, cutout).x;
-    }
-
-    private int getConfigDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
-        return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
-                di.rotation, cutout).y;
-    }
-
-    private static Pair<DisplayInfo, WmDisplayCutout> displayInfoForRotation(int rotation,
-            boolean withDisplayCutout) {
-        return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false);
+    private DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
+        return displayInfoAndCutoutForRotation(
+                rotation, withDisplayCutout, false /* isLongEdgeCutout */);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 34575ae..6951fef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -41,7 +41,6 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.util.Pair;
 import android.view.DisplayInfo;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsState;
@@ -50,8 +49,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.utils.WmDisplayCutout;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -123,7 +120,7 @@
     private void updateDisplayFrames() {
         mFrames = createDisplayFrames(
                 mDisplayContent.getInsetsStateController().getRawInsetsState());
-        mDisplayBounds.set(0, 0, mFrames.mDisplayWidth, mFrames.mDisplayHeight);
+        mDisplayBounds.set(0, 0, mFrames.mWidth, mFrames.mHeight);
         mDisplayContent.mDisplayFrames = mFrames;
         mDisplayContent.setBounds(mDisplayBounds);
         mDisplayPolicy.layoutWindowLw(mNavBarWindow, null, mFrames);
@@ -131,13 +128,13 @@
     }
 
     private DisplayFrames createDisplayFrames(InsetsState insetsState) {
-        final Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation,
+        final DisplayInfo info = displayInfoAndCutoutForRotation(mRotation,
                 mHasDisplayCutout, mIsLongEdgeDisplayCutout);
         final RoundedCorners roundedCorners = mHasRoundedCorners
                 ? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
                 : RoundedCorners.NO_ROUNDED_CORNERS;
-        return new DisplayFrames(mDisplayContent.getDisplayId(),
-                insetsState, info.first, info.second, roundedCorners, new PrivacyIndicatorBounds());
+        return new DisplayFrames(insetsState, info,
+                info.displayCutout, roundedCorners, new PrivacyIndicatorBounds());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 8f2e9b4..e00296f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -35,13 +36,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static com.android.server.wm.utils.WmDisplayCutout.NO_CUTOUT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -286,10 +286,18 @@
                 DisplayPolicy.isOverlappingWithNavBar(targetWin));
     }
 
-    private WindowState createNavigationBarWindow() {
-        final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
-        win.mHasSurface = true;
-        return win;
+    @Test
+    public void testUpdateDisplayConfigurationByDecor() {
+        final WindowState navbar = createNavBarWithProvidedInsets(mDisplayContent);
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        final DisplayInfo di = mDisplayContent.getDisplayInfo();
+        final int prevScreenHeightDp = mDisplayContent.getConfiguration().screenHeightDp;
+        assertTrue(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+        assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
+                di.logicalWidth, di.logicalHeight).mConfigInsets.bottom);
+        mDisplayContent.sendNewConfiguration();
+        assertNotEquals(prevScreenHeightDp, mDisplayContent.getConfiguration().screenHeightDp);
+        assertFalse(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
     }
 
     @UseTestDisplay(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
@@ -307,7 +315,7 @@
         mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
         mImeWindow.mAboveInsetsState.set(state);
-        mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
+        mDisplayContent.mDisplayFrames = new DisplayFrames(
                 state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 97b1c91..fe890d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -30,25 +30,14 @@
 import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
 
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.RectF;
 import android.os.Binder;
-import android.os.IBinder;
-import android.testing.TestableResources;
-import android.util.Pair;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.WindowManagerGlobal;
 
-import com.android.internal.R;
-import com.android.server.wm.utils.WmDisplayCutout;
-
 import org.junit.Before;
 
 public class DisplayPolicyTestsBase extends WindowTestsBase {
@@ -69,23 +58,8 @@
 
         mDisplayPolicy = mDisplayContent.getDisplayPolicy();
         spyOn(mDisplayPolicy);
-
-        final TestContextWrapper context = new TestContextWrapper(
-                mDisplayPolicy.getContext(), mDisplayPolicy.getCurrentUserResources());
-        final TestableResources resources = context.getResourceMocker();
-        resources.addOverride(R.dimen.navigation_bar_height, NAV_BAR_HEIGHT);
-        resources.addOverride(R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT);
-        resources.addOverride(R.dimen.navigation_bar_width, NAV_BAR_HEIGHT);
-        resources.addOverride(R.dimen.navigation_bar_frame_height_landscape, NAV_BAR_HEIGHT);
-        resources.addOverride(R.dimen.navigation_bar_frame_height, NAV_BAR_HEIGHT);
-        doReturn(STATUS_BAR_HEIGHT).when(mDisplayPolicy).getStatusBarHeightForRotation(anyInt());
-        doReturn(resources.getResources()).when(mDisplayPolicy).getCurrentUserResources();
         doReturn(true).when(mDisplayPolicy).hasNavigationBar();
         doReturn(true).when(mDisplayPolicy).hasStatusBar();
-
-        mDisplayContent.getDisplayRotation().configure(DISPLAY_WIDTH, DISPLAY_HEIGHT);
-        mDisplayPolicy.onConfigurationChanged();
-
         addWindow(mStatusBarWindow);
         addWindow(mNavBarWindow);
 
@@ -101,24 +75,20 @@
         win.mHasSurface = true;
     }
 
-    static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation,
-            boolean withDisplayCutout, boolean isLongEdgeCutout) {
-        final DisplayInfo info = new DisplayInfo();
-        WmDisplayCutout cutout = WmDisplayCutout.NO_CUTOUT;
-
+    DisplayInfo displayInfoAndCutoutForRotation(int rotation, boolean withDisplayCutout,
+            boolean isLongEdgeCutout) {
+        final DisplayInfo info = mDisplayContent.getDisplayInfo();
         final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
         info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
         info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
         info.rotation = rotation;
-        if (withDisplayCutout) {
-            cutout = WmDisplayCutout.computeSafeInsets(
-                    displayCutoutForRotation(rotation, isLongEdgeCutout), info.logicalWidth,
-                    info.logicalHeight);
-            info.displayCutout = cutout.getDisplayCutout();
-        } else {
-            info.displayCutout = null;
-        }
-        return Pair.create(info, cutout);
+        mDisplayContent.mInitialDisplayCutout = withDisplayCutout
+                ? displayCutoutForRotation(ROTATION_0, isLongEdgeCutout)
+                : DisplayCutout.NO_CUTOUT;
+        info.displayCutout = mDisplayContent.calculateDisplayCutoutForRotation(rotation);
+        mDisplayContent.updateBaseDisplayMetrics(DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                info.logicalDensityDpi, info.physicalXDpi, info.physicalYDpi);
+        return info;
     }
 
     private static DisplayCutout displayCutoutForRotation(int rotation, boolean isLongEdgeCutout) {
@@ -152,33 +122,4 @@
         return DisplayCutout.fromBoundingRect((int) rectF.left, (int) rectF.top,
                 (int) rectF.right, (int) rectF.bottom, pos);
     }
-
-    static class TestContextWrapper extends ContextWrapper {
-        private final TestableResources mResourceMocker;
-
-        TestContextWrapper(Context targetContext, Resources targetResources) {
-            super(targetContext);
-            mResourceMocker = new TestableResources(targetResources);
-        }
-
-        @Override
-        public int checkPermission(String permission, int pid, int uid) {
-            return PackageManager.PERMISSION_GRANTED;
-        }
-
-        @Override
-        public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
-            return PackageManager.PERMISSION_GRANTED;
-        }
-
-        @Override
-        public Resources getResources() {
-            return mResourceMocker.getResources();
-        }
-
-        TestableResources getResourceMocker() {
-            return mResourceMocker;
-        }
-    }
-
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 892b5f9..89f7111 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -20,6 +20,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
@@ -68,7 +69,6 @@
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.wm.utils.WmDisplayCutout;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -1008,7 +1008,7 @@
             mMockDisplayContent = mock(DisplayContent.class);
             mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay;
             when(mMockDisplayContent.calculateDisplayCutoutForRotation(anyInt()))
-                    .thenReturn(WmDisplayCutout.NO_CUTOUT);
+                    .thenReturn(NO_CUTOUT);
             when(mMockDisplayContent.getDefaultTaskDisplayArea())
                     .thenReturn(mock(TaskDisplayArea.class));
             when(mMockDisplayContent.getWindowConfiguration())
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 f6ffa91..2f1cc20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3133,8 +3133,8 @@
     }
 
     private static void resizeDisplay(DisplayContent displayContent, int width, int height) {
-        displayContent.mBaseDisplayWidth = width;
-        displayContent.mBaseDisplayHeight = height;
+        displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
+                displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
         final Configuration c = new Configuration();
         displayContent.computeScreenConfiguration(c);
         displayContent.onRequestedOverrideConfigurationChanged(c);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index f4323db..f322779 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -36,7 +36,6 @@
 import static android.view.Surface.ROTATION_90;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -692,12 +691,9 @@
         // Setup the display with a top stable inset. The later assertion will ensure the inset is
         // excluded from screenHeightDp.
         final int statusBarHeight = 100;
-        final DisplayPolicy policy = display.getDisplayPolicy();
-        doAnswer(invocationOnMock -> {
-            final Rect insets = invocationOnMock.<Rect>getArgument(0);
-            insets.top = statusBarHeight;
-            return null;
-        }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
+        final DisplayInfo di = display.getDisplayInfo();
+        display.getDisplayPolicy().getDecorInsetsInfo(di.rotation,
+                di.logicalWidth, di.logicalHeight).mConfigInsets.top = statusBarHeight;
 
         // Without limiting to be inside the parent bounds, the out screen size should keep relative
         // to the input bounds.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index f5304d0..aa3ca18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -18,13 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -33,7 +26,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import android.annotation.Nullable;
@@ -47,7 +39,6 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
-import android.view.WindowInsets;
 
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
@@ -209,26 +200,6 @@
                 doReturn(true).when(newDisplay).supportsSystemDecorations();
                 doReturn(true).when(displayPolicy).hasNavigationBar();
                 doReturn(NAV_BAR_BOTTOM).when(displayPolicy).navigationBarPosition(anyInt());
-                doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsets(any(),
-                        eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()));
-                doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsets(any(),
-                        eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()
-                                | WindowInsets.Type.statusBars()));
-                final int[] nonDecorTypes = new int[]{
-                        ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
-                        ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
-                };
-                doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
-                        any(),
-                        eq(nonDecorTypes));
-                final int[] stableTypes = new int[]{
-                        ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
-                        ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT,
-                        ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
-                };
-                doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
-                        any(),
-                        eq(stableTypes));
             } else {
                 doReturn(false).when(displayPolicy).hasNavigationBar();
                 doReturn(false).when(displayPolicy).hasStatusBar();
@@ -241,11 +212,6 @@
             displayPolicy.finishScreenTurningOn();
             if (mStatusBarHeight > 0) {
                 doReturn(true).when(displayPolicy).hasStatusBar();
-                doAnswer(invocation -> {
-                    Rect inOutInsets = (Rect) invocation.getArgument(0);
-                    inOutInsets.top = mStatusBarHeight;
-                    return null;
-                }).when(displayPolicy).convertNonDecorInsetsToStableInsets(any(), anyInt());
             }
             Configuration c = new Configuration();
             newDisplay.computeScreenConfiguration(c);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 8288713..6333508 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -52,6 +52,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.InsetsState;
@@ -63,8 +64,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.utils.WmDisplayCutout;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -164,8 +163,8 @@
         // Apply the fixed transform
         Configuration config = new Configuration();
         final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
-        final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
-        final DisplayFrames displayFrames = new DisplayFrames(dc.getDisplayId(), new InsetsState(),
+        final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
+        final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(),
                 info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
         wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 77e12f4..b6373b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -26,6 +26,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Process.SYSTEM_UID;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
@@ -69,6 +70,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManager;
@@ -84,6 +86,7 @@
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
 import android.view.IWindow;
+import android.view.InsetsFrameProvider;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsVisibilities;
@@ -417,6 +420,15 @@
                 true /* ownerCanManageAppTokens */);
     }
 
+    WindowState createNavBarWithProvidedInsets(DisplayContent dc) {
+        final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, dc, "navbar");
+        navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+                new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, NAV_BAR_HEIGHT))
+        };
+        dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+        return navbar;
+    }
+
     WindowState createAppWindow(Task task, int type, String name) {
         final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent());
         task.addChild(activity, 0);
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index e882b25..418f518 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -987,8 +987,17 @@
     /** The ePDG does not support un-authenticated IMSI based emergency PDN bringup **/
     public static final int IWLAN_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED = 0x2B2F;
 
-    // Device is unable to establish an IPSec tunnel with the ePDG for any reason
-    // e.g authentication fail or certificate validation or DNS Resolution and timeout failure.
+    // The below error causes are relevant when the device is unable to establish an IPSec tunnel
+    // with the ePDG for any reason, e.g. authentication fail or certificate validation or DNS
+    // Resolution and timeout failure.
+
+    /**
+     * The requested service was rejected because of congestion in the network while accessing the
+     * IWLAN ePDG. Defined in 3GPP TS 24.502, Section 9.2.4.2.
+     *
+     * @hide
+     */
+    public static final int IWLAN_CONGESTION = 0x3C8C;
 
     /** IKE configuration error resulting in failure  */
     public static final int IWLAN_IKEV2_CONFIG_FAILURE = 0x4000;
diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index 78660c0..fffe33a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -24,6 +24,20 @@
 android_test {
     name: "FlickerTestApp",
     srcs: ["**/*.java"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.appcompat_appcompat",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.core_core",
+        "androidx.fragment_fragment",
+        "androidx.recyclerview_recyclerview",
+        "androidx.navigation_navigation-common-ktx",
+        "androidx.navigation_navigation-fragment-ktx",
+        "androidx.navigation_navigation-runtime-ktx",
+        "androidx.navigation_navigation-ui-ktx",
+        "kotlin-stdlib",
+        "kotlinx-coroutines-android",
+    ],
     sdk_version: "current",
     test_suites: ["device-tests"],
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 3e2130d..25216a3 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -163,5 +163,15 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".MailActivity"
+            android:exported="true"
+            android:label="MailApp"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
+            android:theme="@style/Theme.AppCompat.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml b/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml
new file mode 100644
index 0000000..c0165e5
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<set
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/linear_interpolator"
+    android:shareInterpolator="false">
+    <translate
+        android:duration="500"
+        android:fromYDelta="100%p"
+        android:toYDelta="0%p" />
+</set>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml
new file mode 100644
index 0000000..8a97433
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <fragment
+        android:id="@+id/main_fragment"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:defaultNavHost="true"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:navGraph="@navigation/mail_navigation"></fragment>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml
new file mode 100644
index 0000000..fbbdc5b
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml
new file mode 100644
index 0000000..0e30745
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/mail_recycle_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml
new file mode 100644
index 0000000..c39bfb3
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/list_item_height"
+    android:layout_marginLeft="@dimen/margin_medium"
+    android:layout_marginRight="@dimen/margin_medium">
+    <ImageView
+        android:id="@+id/mail_row_item_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingBottom="@dimen/padding_small"
+        android:paddingEnd="@dimen/padding_small"
+        android:paddingStart="@dimen/padding_small"
+        android:paddingTop="@dimen/padding_small"/>
+    <TextView
+        android:id="@+id/mail_row_item_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:paddingEnd="@dimen/padding_small"
+        android:textSize="@dimen/list_item_text_size" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml b/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml
new file mode 100644
index 0000000..d12707a
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/mail_navigation"
+    app:startDestination="@id/fragment_mail_list">
+    <fragment
+        android:id="@+id/fragment_mail_list"
+        android:name=".MailListFragment"
+        android:label="Mail List">
+        <action
+            android:id="@+id/action_mail_list_to_mail_content"
+            app:destination="@id/fragment_mail_content"
+            app:enterAnim="@anim/slide_in_from_bottom" />
+    </fragment>
+    <fragment
+        android:id="@+id/fragment_mail_content"
+        android:name=".MailContentFragment"
+        android:label="Mail Content">
+    </fragment>
+</navigation>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml
new file mode 100644
index 0000000..0b0763d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <dimen name="padding_small">8dp</dimen>
+    <dimen name="margin_medium">16dp</dimen>
+    <dimen name="list_item_height">72dp</dimen>
+    <dimen name="list_item_text_size">24dp</dimen>
+    <dimen name="icon_size">64dp</dimen>
+    <dimen name="icon_text_size">36dp</dimen>
+</resources>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java
new file mode 100644
index 0000000..419d438
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MailActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_mail);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java
new file mode 100644
index 0000000..984263a
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import static androidx.navigation.fragment.NavHostFragment.findNavController;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.shapes.Shape;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.navigation.NavController;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class MailAdapter extends RecyclerView.Adapter<MailAdapter.ViewHolder> {
+
+    private final Fragment mFragment;
+    private final int mSize;
+
+    static class BadgeShape extends OvalShape {
+        Context mContext;
+        String mLabel;
+
+        BadgeShape(Context context, String label) {
+            mContext = context;
+            mLabel = label;
+        }
+
+        @Override
+        public void draw(Canvas canvas, Paint paint) {
+            final Resources resources = mContext.getResources();
+            int textSize = resources.getDimensionPixelSize(R.dimen.icon_text_size);
+            paint.setColor(Color.BLACK);
+            super.draw(canvas, paint);
+            paint.setColor(Color.WHITE);
+            paint.setTextSize(textSize);
+            paint.setTypeface(Typeface.DEFAULT_BOLD);
+            paint.setTextAlign(Paint.Align.CENTER);
+            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
+            float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
+            canvas.drawText(
+                    mLabel,
+                    rect().centerX(),
+                    rect().centerY() + distance,
+                    paint);
+        }
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+
+        NavController mNavController;
+        ImageView mImageView;
+        TextView mTextView;
+        DisplayMetrics mDisplayMetrics;
+
+        ViewHolder(@NonNull View itemView, NavController navController) {
+            super(itemView);
+            mNavController = navController;
+            itemView.setOnClickListener(this::onClick);
+            mImageView = itemView.findViewById(R.id.mail_row_item_icon);
+            mTextView = itemView.findViewById(R.id.mail_row_item_text);
+            mDisplayMetrics = itemView.getContext().getResources().getDisplayMetrics();
+        }
+
+        void onClick(View v) {
+            mNavController.navigate(R.id.action_mail_list_to_mail_content);
+        }
+
+        public void setContent(int i) {
+            final Resources resources = mImageView.getContext().getResources();
+            final int badgeSize = resources.getDimensionPixelSize(R.dimen.icon_size);
+            final char c = (char) ('A' + i % 26);
+            final Shape badge = new BadgeShape(mImageView.getContext(), String.valueOf(c));
+            ShapeDrawable drawable = new ShapeDrawable();
+            drawable.setIntrinsicHeight(badgeSize);
+            drawable.setIntrinsicWidth(badgeSize);
+            drawable.setShape(badge);
+            mImageView.setImageDrawable(drawable);
+            mTextView.setText(String.format("%s-%04d", c, i));
+        }
+    }
+
+    public MailAdapter(Fragment fragment, int size) {
+        super();
+        mFragment = fragment;
+        mSize = size;
+    }
+
+    @NonNull
+    @Override
+    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+        View v = LayoutInflater
+                .from(viewGroup.getContext())
+                .inflate(R.layout.mail_row_item, viewGroup, false);
+        return new ViewHolder(v, findNavController(mFragment));
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
+        viewHolder.setContent(i);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSize;
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java
new file mode 100644
index 0000000..278b92e
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+public class MailContentFragment extends Fragment {
+    public MailContentFragment() {
+        super(R.layout.fragment_mail_content);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        view.setBackgroundColor(Color.LTGRAY);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java
new file mode 100644
index 0000000..551820c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class MailListFragment extends Fragment {
+
+    protected RecyclerView mRecyclerView;
+    protected RecyclerView.LayoutManager mLayoutManager;
+    protected MailAdapter mAdapter;
+
+    public MailListFragment() {
+        super(R.layout.fragment_mail_list);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_mail_list, container, false);
+        mRecyclerView = rootView.findViewById(R.id.mail_recycle_view);
+        mAdapter = new MailAdapter(this, 1000);
+        mRecyclerView.setAdapter(mAdapter);
+        mLayoutManager = new LinearLayoutManager(getActivity());
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        return rootView;
+    }
+}