Merge "Add null-check for lockscreenSmartspace" 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/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index ba0546a..3979c6c 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -171,7 +171,7 @@
         mPassword = password;
         mRsaPrivateKey = rsaPrivateKey;
         mUserCert = userCert;
-        mProxyInfo = new ProxyInfo(proxyInfo);
+        mProxyInfo = (proxyInfo == null) ? null : new ProxyInfo(proxyInfo);
 
         // UnmodifiableList doesn't make a defensive copy by default.
         mAllowedAlgorithms = Collections.unmodifiableList(new ArrayList<>(allowedAlgorithms));
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/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/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index dc1f612..8ca763e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -397,6 +397,8 @@
         private @Surface.Rotation int mEndFixedRotation = ROTATION_UNDEFINED;
         private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
         private @ColorInt int mBackgroundColor;
+        private SurfaceControl mSnapshot = null;
+        private float mSnapshotLuma;
 
         public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
             mContainer = container;
@@ -420,6 +422,8 @@
             mEndFixedRotation = in.readInt();
             mRotationAnimation = in.readInt();
             mBackgroundColor = in.readInt();
+            mSnapshot = in.readTypedObject(SurfaceControl.CREATOR);
+            mSnapshotLuma = in.readFloat();
         }
 
         /** Sets the parent of this change's container. The parent must be a participant or null. */
@@ -489,6 +493,12 @@
             mBackgroundColor = backgroundColor;
         }
 
+        /** Sets a snapshot surface for the "start" state of the container. */
+        public void setSnapshot(@Nullable SurfaceControl snapshot, float luma) {
+            mSnapshot = snapshot;
+            mSnapshotLuma = luma;
+        }
+
         /** @return the container that is changing. May be null if non-remotable (eg. activity) */
         @Nullable
         public WindowContainerToken getContainer() {
@@ -587,6 +597,17 @@
             return mBackgroundColor;
         }
 
+        /** @return a snapshot surface (if applicable). */
+        @Nullable
+        public SurfaceControl getSnapshot() {
+            return mSnapshot;
+        }
+
+        /** @return the luma calculated for the snapshot surface (if applicable). */
+        public float getSnapshotLuma() {
+            return mSnapshotLuma;
+        }
+
         /** @hide */
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -605,6 +626,8 @@
             dest.writeInt(mEndFixedRotation);
             dest.writeInt(mRotationAnimation);
             dest.writeInt(mBackgroundColor);
+            dest.writeTypedObject(mSnapshot, flags);
+            dest.writeFloat(mSnapshotLuma);
         }
 
         @NonNull
@@ -629,11 +652,13 @@
 
         @Override
         public String toString() {
-            return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+            String out = "{" + mContainer + "(" + mParent + ") leash=" + mLeash
                     + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
                     + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
                     + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation
-                    + " endFixedRotation=" + mEndFixedRotation + "}";
+                    + " endFixedRotation=" + mEndFixedRotation;
+            if (mSnapshot != null) out += " snapshot=" + mSnapshot;
+            return out + "}";
         }
     }
 
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/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index 527286cf0..a065e2b 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -287,7 +287,6 @@
         int dividerMax = isHorizontalDivision
                 ? mDisplayHeight
                 : mDisplayWidth;
-        int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
         int startPos = -mDividerSize;
         if (dockedSide == DOCKED_RIGHT) {
             startPos += mInsets.left;
@@ -308,8 +307,7 @@
                 addMinimizedTarget(isHorizontalDivision, dockedSide);
                 break;
         }
-        mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
-                SnapTarget.FLAG_DISMISS_END, 0.35f));
+        mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
     }
 
     private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
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/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/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f6499f8..ce6df20 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3193,12 +3193,6 @@
       "group": "WM_DEBUG_LOCKTASK",
       "at": "com\/android\/server\/wm\/LockTaskController.java"
     },
-    "956467125": {
-      "message": "Reparenting Activity to embedded TaskFragment, but the Activity is not collected",
-      "level": "WARN",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
-    },
     "958338552": {
       "message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found",
       "level": "VERBOSE",
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..7d34ea4 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
@@ -62,6 +62,9 @@
     private void onInit() {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
         mSettingsObserver.observe();
+        if (DesktopMode.isActive(mContext)) {
+            updateDesktopModeActive(true);
+        }
     }
 
     @VisibleForTesting
@@ -73,14 +76,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/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 1958157..979b7c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -207,6 +207,9 @@
             }
         });
 
+        // this disables the ripples
+        mEnterSplitButton.setEnabled(false);
+
         findViewById(R.id.resize_handle).setAlpha(0);
 
         mActionsGroup = findViewById(R.id.actions_group);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 93c7529..3fef823 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -32,7 +32,7 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM_SHELL),
-    WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+    WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM_SHELL),
     WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_STARTING_WINDOW),
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 199007d..dcfe08d 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
@@ -579,8 +579,10 @@
                     IRemoteAnimationFinishedCallback finishedCallback,
                     SurfaceControl.Transaction t) {
                 if (apps == null || apps.length == 0) {
-                    onRemoteAnimationFinished(apps);
+                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+                    setDividerVisibility(true, t);
                     t.apply();
+                    onRemoteAnimationFinished(apps);
                     try {
                         adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
                     } catch (RemoteException e) {
@@ -593,7 +595,7 @@
                 nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
                         getDividerBarLegacyTarget());
 
-                updateSurfaceBounds(mSplitLayout, t, false);
+                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
                 setDividerVisibility(true, t);
                 for (int i = 0; i < apps.length; ++i) {
                     if (apps[i].mode == MODE_OPENING) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index cff60f5..4c927b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -554,6 +554,12 @@
     private void edgeExtendWindow(TransitionInfo.Change change,
             Animation a, SurfaceControl.Transaction startTransaction,
             SurfaceControl.Transaction finishTransaction) {
+        // Do not create edge extension surface for transfer starting window change.
+        // The app surface could be empty thus nothing can draw on the hardware renderer, which will
+        // block this thread when calling Surface#unlockCanvasAndPost.
+        if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
+            return;
+        }
         final Transformation transformationAtStart = new Transformation();
         a.getTransformationAt(0, transformationAtStart);
         final Transformation transformationAtEnd = new Transformation();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 6388ca1..b647f43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -139,39 +139,48 @@
                 .build();
 
         try {
-            SurfaceControl.LayerCaptureArgs args =
-                    new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl)
-                            .setCaptureSecureLayers(true)
-                            .setAllowProtected(true)
-                            .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
-                            .build();
-            SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
-                    SurfaceControl.captureLayers(args);
-            if (screenshotBuffer == null) {
-                Slog.w(TAG, "Unable to take screenshot of display");
-                return;
-            }
+            if (change.getSnapshot() != null) {
+                mScreenshotLayer = change.getSnapshot();
+                t.reparent(mScreenshotLayer, mAnimLeash);
+                mStartLuma = change.getSnapshotLuma();
+            } else {
+                SurfaceControl.LayerCaptureArgs args =
+                        new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl)
+                                .setCaptureSecureLayers(true)
+                                .setAllowProtected(true)
+                                .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
+                                .build();
+                SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                        SurfaceControl.captureLayers(args);
+                if (screenshotBuffer == null) {
+                    Slog.w(TAG, "Unable to take screenshot of display");
+                    return;
+                }
 
-            mScreenshotLayer = new SurfaceControl.Builder(session)
-                    .setParent(mAnimLeash)
-                    .setBLASTLayer()
-                    .setSecure(screenshotBuffer.containsSecureLayers())
-                    .setOpaque(true)
-                    .setCallsite("ShellRotationAnimation")
-                    .setName("RotationLayer")
-                    .build();
+                mScreenshotLayer = new SurfaceControl.Builder(session)
+                        .setParent(mAnimLeash)
+                        .setBLASTLayer()
+                        .setSecure(screenshotBuffer.containsSecureLayers())
+                        .setOpaque(true)
+                        .setCallsite("ShellRotationAnimation")
+                        .setName("RotationLayer")
+                        .build();
+
+                final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
+                final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+                t.setDataSpace(mScreenshotLayer, colorSpace.getDataSpace());
+                t.setBuffer(mScreenshotLayer, hardwareBuffer);
+                t.show(mScreenshotLayer);
+                if (!isCustomRotate()) {
+                    mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace);
+                }
+            }
 
             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
             t.show(mAnimLeash);
             // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
             t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
 
-            final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
-            final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-            t.setDataSpace(mScreenshotLayer, colorSpace.getDataSpace());
-            t.setBuffer(mScreenshotLayer, hardwareBuffer);
-            t.show(mScreenshotLayer);
-
             if (!isCustomRotate()) {
                 mBackColorSurface = new SurfaceControl.Builder(session)
                         .setParent(rootLeash)
@@ -181,8 +190,6 @@
                         .setName("BackColorSurface")
                         .build();
 
-                mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace);
-
                 t.setLayer(mBackColorSurface, -1);
                 t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
                 t.show(mBackColorSurface);
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/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..f50dc74 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -212,6 +212,7 @@
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
         "kotlinx_coroutines_test",
+        "kotlin-reflect",
         "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
@@ -240,11 +241,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/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e5e369e..4136b04 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1464,14 +1464,14 @@
          if their end is aligned with the parent end. Represented as the percentage over from the
          start of the parent container. -->
     <item name="dream_overlay_complication_guide_end_percent" format="float" type="dimen">
-        0.75
+        0.5
     </item>
 
     <!-- The position of the start guide, which dream overlay complications can align their end to
          if their start is aligned with the parent start. Represented as the percentage over from
          the start of the parent container. -->
     <item name="dream_overlay_complication_guide_start_percent" format="float" type="dimen">
-        0.25
+        0.5
     </item>
 
     <!-- The position of the bottom guide, which dream overlay complications can align their top to
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b33cea7..53f1227 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>
@@ -1861,10 +1862,10 @@
     <!-- URL for care instructions for overheating devices -->
     <string name="high_temp_dialog_help_url" translatable="false"></string>
 
-    <!-- Title for alarm dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=30] -->
-    <string name="high_temp_alarm_title">Unplug charger</string>
-    <!-- Text body for dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=300] -->
-    <string name="high_temp_alarm_notify_message">There\u2019s an issue charging this device. Unplug the power adapter, and take care as the cable may be warm.</string>
+    <!-- Title for alarm dialog alerting user the usb charger or accessorry has reached a certain temperature that should unplug the device immediately. [CHAR LIMIT=30] -->
+    <string name="high_temp_alarm_title">Unplug your device</string>
+    <!-- Text body for dialog alerting user the usb charger or accessorry has reached a certain temperature that should unplug the device immediately. [CHAR LIMIT=300] -->
+    <string name="high_temp_alarm_notify_message">Your device is getting warm near the charging port. If it\u2019s connected to a charger or USB accessory, unplug it, and take care as the cable may also be warm.</string>
     <!-- Text for See care steps button [CHAR LIMIT=300] -->
     <string name="high_temp_alarm_help_care_steps">See care steps</string>
     <!-- Text link directs to usb overheat help page. -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6b2ff37..a36a518 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -971,6 +971,8 @@
         <!-- Padding for indeterminate progress bar -->
         <item name="progressBarStartPadding">12dp</item>
         <item name="progressBarEndPadding">16dp</item>
+
+        <item name="iconSize">25dp</item>
     </style>
 
     <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
new file mode 100644
index 0000000..6fcb6f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -0,0 +1,255 @@
+/*
+ * 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.keyguard
+
+import android.annotation.StringDef
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
+import com.android.keyguard.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
+import com.android.keyguard.FaceAuthApiRequestReason.Companion.QS_EXPANDED
+import com.android.keyguard.FaceAuthApiRequestReason.Companion.SWIPE_UP_ON_BOUNCER
+import com.android.keyguard.FaceAuthApiRequestReason.Companion.UDFPS_POINTER_DOWN
+import com.android.keyguard.InternalFaceAuthReasons.ALL_AUTHENTICATORS_REGISTERED
+import com.android.keyguard.InternalFaceAuthReasons.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.keyguard.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED
+import com.android.keyguard.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION
+import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED
+import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED
+import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
+import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED
+import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED
+import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
+import com.android.keyguard.InternalFaceAuthReasons.FACE_AUTHENTICATED
+import com.android.keyguard.InternalFaceAuthReasons.FACE_AUTH_STOPPED_ON_USER_INPUT
+import com.android.keyguard.InternalFaceAuthReasons.FACE_CANCEL_NOT_RECEIVED
+import com.android.keyguard.InternalFaceAuthReasons.FACE_LOCKOUT_RESET
+import com.android.keyguard.InternalFaceAuthReasons.FINISHED_GOING_TO_SLEEP
+import com.android.keyguard.InternalFaceAuthReasons.FP_AUTHENTICATED
+import com.android.keyguard.InternalFaceAuthReasons.FP_LOCKED_OUT
+import com.android.keyguard.InternalFaceAuthReasons.GOING_TO_SLEEP
+import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_GOING_AWAY
+import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_INIT
+import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
+import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET
+import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
+import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
+import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
+import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
+import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
+import com.android.keyguard.InternalFaceAuthReasons.STARTED_WAKING_UP
+import com.android.keyguard.InternalFaceAuthReasons.TRUST_DISABLED
+import com.android.keyguard.InternalFaceAuthReasons.TRUST_ENABLED
+import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
+
+/**
+ * List of reasons why face auth is requested by clients through
+ * [KeyguardUpdateMonitor.requestFaceAuth].
+ */
+@Retention(AnnotationRetention.SOURCE)
+@StringDef(
+    SWIPE_UP_ON_BOUNCER,
+    UDFPS_POINTER_DOWN,
+    NOTIFICATION_PANEL_CLICKED,
+    QS_EXPANDED,
+    PICK_UP_GESTURE_TRIGGERED,
+)
+annotation class FaceAuthApiRequestReason {
+    companion object {
+        const val SWIPE_UP_ON_BOUNCER = "Face auth due to swipe up on bouncer"
+        const val UDFPS_POINTER_DOWN = "Face auth triggered due to finger down on UDFPS"
+        const val NOTIFICATION_PANEL_CLICKED = "Face auth due to notification panel click."
+        const val QS_EXPANDED = "Face auth due to QS expansion."
+        const val PICK_UP_GESTURE_TRIGGERED =
+            "Face auth due to pickup gesture triggered when the device is awake and not from AOD."
+    }
+}
+
+/** List of events why face auth could be triggered by [KeyguardUpdateMonitor]. */
+private object InternalFaceAuthReasons {
+    const val OCCLUDING_APP_REQUESTED = "Face auth due to request from occluding app."
+    const val RETRY_AFTER_HW_UNAVAILABLE = "Face auth due to retry after hardware unavailable."
+    const val FACE_LOCKOUT_RESET = "Face auth due to face lockout reset."
+    const val DEVICE_WOKEN_UP_ON_REACH_GESTURE =
+        "Face auth requested when user reaches for the device on AoD."
+    const val ALTERNATE_BIOMETRIC_BOUNCER_SHOWN = "Face auth due to alternate bouncer shown."
+    const val PRIMARY_BOUNCER_SHOWN = "Face auth started/stopped due to primary bouncer shown."
+    const val PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN =
+        "Face auth started/stopped due to bouncer being shown or will be shown."
+    const val TRUST_DISABLED = "Face auth started due to trust disabled."
+    const val TRUST_ENABLED = "Face auth stopped due to trust enabled."
+    const val KEYGUARD_OCCLUSION_CHANGED =
+        "Face auth started/stopped due to keyguard occlusion change."
+    const val ASSISTANT_VISIBILITY_CHANGED =
+        "Face auth started/stopped due to assistant visibility change."
+    const val STARTED_WAKING_UP = "Face auth started/stopped due to device starting to wake up."
+    const val DREAM_STOPPED = "Face auth due to dream stopped."
+    const val ALL_AUTHENTICATORS_REGISTERED = "Face auth due to all authenticators registered."
+    const val ENROLLMENTS_CHANGED = "Face auth due to enrolments changed."
+    const val KEYGUARD_VISIBILITY_CHANGED =
+        "Face auth stopped or started due to keyguard visibility changed."
+    const val FACE_CANCEL_NOT_RECEIVED = "Face auth stopped due to face cancel signal not received."
+    const val AUTH_REQUEST_DURING_CANCELLATION =
+        "Another request to start face auth received while cancelling face auth"
+    const val DREAM_STARTED = "Face auth stopped because dreaming started"
+    const val FP_LOCKED_OUT = "Face auth stopped because fp locked out"
+    const val FACE_AUTH_STOPPED_ON_USER_INPUT =
+        "Face auth stopped because user started typing password/pin"
+    const val KEYGUARD_GOING_AWAY = "Face auth stopped because keyguard going away"
+    const val CAMERA_LAUNCHED = "Face auth started/stopped because camera launched"
+    const val FP_AUTHENTICATED = "Face auth started/stopped because fingerprint launched"
+    const val GOING_TO_SLEEP = "Face auth started/stopped because going to sleep"
+    const val FINISHED_GOING_TO_SLEEP = "Face auth stopped because finished going to sleep"
+    const val KEYGUARD_INIT = "Face auth started/stopped because Keyguard is initialized"
+    const val KEYGUARD_RESET = "Face auth started/stopped because Keyguard is reset"
+    const val USER_SWITCHING = "Face auth started/stopped because user is switching"
+    const val FACE_AUTHENTICATED = "Face auth started/stopped because face is authenticated"
+    const val BIOMETRIC_ENABLED =
+        "Face auth started/stopped because biometric is enabled on keyguard"
+}
+
+/** UiEvents that are logged to identify why face auth is being triggered. */
+enum class FaceAuthUiEvent constructor(private val id: Int, val reason: String) :
+    UiEventLogger.UiEventEnum {
+    @UiEvent(doc = OCCLUDING_APP_REQUESTED)
+    FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED(1146, OCCLUDING_APP_REQUESTED),
+
+    @UiEvent(doc = UDFPS_POINTER_DOWN)
+    FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN(1147, UDFPS_POINTER_DOWN),
+
+    @UiEvent(doc = SWIPE_UP_ON_BOUNCER)
+    FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER(1148, SWIPE_UP_ON_BOUNCER),
+
+    @UiEvent(doc = DEVICE_WOKEN_UP_ON_REACH_GESTURE)
+    FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD(1149, DEVICE_WOKEN_UP_ON_REACH_GESTURE),
+
+    @UiEvent(doc = FACE_LOCKOUT_RESET)
+    FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET(1150, FACE_LOCKOUT_RESET),
+
+    @UiEvent(doc = QS_EXPANDED)
+    FACE_AUTH_TRIGGERED_QS_EXPANDED(1151, QS_EXPANDED),
+
+    @UiEvent(doc = NOTIFICATION_PANEL_CLICKED)
+    FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED(1152, NOTIFICATION_PANEL_CLICKED),
+
+    @UiEvent(doc = PICK_UP_GESTURE_TRIGGERED)
+    FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED(1153, PICK_UP_GESTURE_TRIGGERED),
+
+    @UiEvent(doc = ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+    FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN(1154,
+        ALTERNATE_BIOMETRIC_BOUNCER_SHOWN),
+
+    @UiEvent(doc = PRIMARY_BOUNCER_SHOWN)
+    FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN(1155, PRIMARY_BOUNCER_SHOWN),
+
+    @UiEvent(doc = PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN)
+    FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN(
+        1197,
+        PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
+    ),
+
+    @UiEvent(doc = RETRY_AFTER_HW_UNAVAILABLE)
+    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE(1156, RETRY_AFTER_HW_UNAVAILABLE),
+
+    @UiEvent(doc = TRUST_DISABLED)
+    FACE_AUTH_TRIGGERED_TRUST_DISABLED(1158, TRUST_DISABLED),
+
+    @UiEvent(doc = TRUST_ENABLED)
+    FACE_AUTH_STOPPED_TRUST_ENABLED(1173, TRUST_ENABLED),
+
+    @UiEvent(doc = KEYGUARD_OCCLUSION_CHANGED)
+    FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED(1159, KEYGUARD_OCCLUSION_CHANGED),
+
+    @UiEvent(doc = ASSISTANT_VISIBILITY_CHANGED)
+    FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED(1160, ASSISTANT_VISIBILITY_CHANGED),
+
+    @UiEvent(doc = STARTED_WAKING_UP)
+    FACE_AUTH_UPDATED_STARTED_WAKING_UP(1161, STARTED_WAKING_UP),
+
+    @UiEvent(doc = DREAM_STOPPED)
+    FACE_AUTH_TRIGGERED_DREAM_STOPPED(1162, DREAM_STOPPED),
+
+    @UiEvent(doc = ALL_AUTHENTICATORS_REGISTERED)
+    FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED(1163, ALL_AUTHENTICATORS_REGISTERED),
+
+    @UiEvent(doc = ENROLLMENTS_CHANGED)
+    FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED(1164, ENROLLMENTS_CHANGED),
+
+    @UiEvent(doc = KEYGUARD_VISIBILITY_CHANGED)
+    FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED(1165, KEYGUARD_VISIBILITY_CHANGED),
+
+    @UiEvent(doc = FACE_CANCEL_NOT_RECEIVED)
+    FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED(1174, FACE_CANCEL_NOT_RECEIVED),
+
+    @UiEvent(doc = AUTH_REQUEST_DURING_CANCELLATION)
+    FACE_AUTH_TRIGGERED_DURING_CANCELLATION(1175, AUTH_REQUEST_DURING_CANCELLATION),
+
+    @UiEvent(doc = DREAM_STARTED)
+    FACE_AUTH_STOPPED_DREAM_STARTED(1176, DREAM_STARTED),
+
+    @UiEvent(doc = FP_LOCKED_OUT)
+    FACE_AUTH_STOPPED_FP_LOCKED_OUT(1177, FP_LOCKED_OUT),
+
+    @UiEvent(doc = FACE_AUTH_STOPPED_ON_USER_INPUT)
+    FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER(1178, FACE_AUTH_STOPPED_ON_USER_INPUT),
+
+    @UiEvent(doc = KEYGUARD_GOING_AWAY)
+    FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY(1179, KEYGUARD_GOING_AWAY),
+
+    @UiEvent(doc = CAMERA_LAUNCHED)
+    FACE_AUTH_UPDATED_CAMERA_LAUNCHED(1180, CAMERA_LAUNCHED),
+
+    @UiEvent(doc = FP_AUTHENTICATED)
+    FACE_AUTH_UPDATED_FP_AUTHENTICATED(1181, FP_AUTHENTICATED),
+
+    @UiEvent(doc = GOING_TO_SLEEP)
+    FACE_AUTH_UPDATED_GOING_TO_SLEEP(1182, GOING_TO_SLEEP),
+
+    @UiEvent(doc = FINISHED_GOING_TO_SLEEP)
+    FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP(1183, FINISHED_GOING_TO_SLEEP),
+
+    @UiEvent(doc = KEYGUARD_INIT)
+    FACE_AUTH_UPDATED_ON_KEYGUARD_INIT(1189, KEYGUARD_INIT),
+
+    @UiEvent(doc = KEYGUARD_RESET)
+    FACE_AUTH_UPDATED_KEYGUARD_RESET(1185, KEYGUARD_RESET),
+
+    @UiEvent(doc = USER_SWITCHING)
+    FACE_AUTH_UPDATED_USER_SWITCHING(1186, USER_SWITCHING),
+
+    @UiEvent(doc = FACE_AUTHENTICATED)
+    FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED(1187, FACE_AUTHENTICATED),
+
+    @UiEvent(doc = BIOMETRIC_ENABLED)
+    FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED);
+
+    override fun getId(): Int = this.id
+}
+
+private val apiRequestReasonToUiEvent =
+    mapOf(
+        SWIPE_UP_ON_BOUNCER to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+        UDFPS_POINTER_DOWN to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN,
+        NOTIFICATION_PANEL_CLICKED to
+            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED,
+        QS_EXPANDED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED,
+        PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED,
+    )
+
+/** Converts the [reason] to the corresponding [FaceAuthUiEvent]. */
+fun apiRequestReasonToUiEvent(@FaceAuthApiRequestReason reason: String): FaceAuthUiEvent =
+    apiRequestReasonToUiEvent[reason]!!
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/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 93175e1..57058b7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -223,7 +223,7 @@
         @Override
         public void onSwipeUp() {
             if (!mUpdateMonitor.isFaceDetectionRunning()) {
-                mUpdateMonitor.requestFaceAuth(true);
+                mUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
                 mKeyguardSecurityCallback.userActivity();
                 showMessage(null, null);
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 167d8af..4984300 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.ACTION_USER_REMOVED;
@@ -32,11 +33,43 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FP_LOCKED_OUT;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_TRUST_ENABLED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DREAM_STOPPED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DURING_CANCELLATION;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_TRUST_DISABLED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_CAMERA_LAUNCHED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_FP_AUTHENTICATED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_GOING_TO_SLEEP;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_RESET;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
@@ -92,6 +125,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
@@ -105,6 +140,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -130,6 +166,7 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Watches for updates that may be interesting to the keyguard, and provides
@@ -240,6 +277,7 @@
     private final boolean mIsPrimaryUser;
     private final AuthController mAuthController;
     private final StatusBarStateController mStatusBarStateController;
+    private final UiEventLogger mUiEventLogger;
     private int mStatusBarState;
     private final StatusBarStateController.StateListener mStatusBarStateControllerListener =
             new StatusBarStateController.StateListener() {
@@ -305,7 +343,6 @@
     private KeyguardBypassController mKeyguardBypassController;
     private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
     private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-    private boolean mIsFaceAuthUserRequested;
     private LockPatternUtils mLockPatternUtils;
     private final IDreamManager mDreamManager;
     private boolean mIsDreaming;
@@ -337,6 +374,7 @@
     protected final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
 
     private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
+    private final Provider<SessionTracker> mSessionTrackerProvider;
 
     @VisibleForTesting
     protected Handler getHandler() {
@@ -352,7 +390,8 @@
                 public void onChanged(boolean enabled, int userId) throws RemoteException {
                     mHandler.post(() -> {
                         mBiometricEnabledForUser.put(userId, enabled);
-                        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+                        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                                FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD);
                     });
                 }
             };
@@ -420,9 +459,11 @@
         // authenticating.  TrustManager sends an onTrustChanged whenever a user unlocks keyguard,
         // for this reason we need to make sure to not authenticate.
         if (wasTrusted == enabled || enabled) {
-            updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
+            updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
+                    FACE_AUTH_STOPPED_TRUST_ENABLED);
         } else {
-            updateBiometricListeningState(BIOMETRIC_ACTION_START);
+            updateBiometricListeningState(BIOMETRIC_ACTION_START,
+                    FACE_AUTH_TRIGGERED_TRUST_DISABLED);
         }
 
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -616,7 +657,7 @@
     public void setKeyguardGoingAway(boolean goingAway) {
         mKeyguardGoingAway = goingAway;
         // This is set specifically to stop face authentication from running.
-        updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
+        updateBiometricListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
     }
 
     /**
@@ -624,7 +665,8 @@
      */
     public void setKeyguardOccluded(boolean occluded) {
         mKeyguardOccluded = occluded;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
     }
 
 
@@ -636,7 +678,8 @@
      */
     public void requestFaceAuthOnOccludingApp(boolean request) {
         mOccludingAppRequestingFace = request;
-        updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
     }
 
     /**
@@ -655,7 +698,8 @@
      */
     public void onCameraLaunched() {
         mSecureCameraLaunched = true;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_CAMERA_LAUNCHED);
     }
 
     /**
@@ -697,7 +741,8 @@
         }
         // Don't send cancel if authentication succeeds
         mFingerprintCancelSignal = null;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_FP_AUTHENTICATED);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -860,7 +905,7 @@
             if (isUdfpsEnrolled()) {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
             }
-            stopListeningForFace();
+            stopListeningForFace(FACE_AUTH_STOPPED_FP_LOCKED_OUT);
         }
 
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -939,7 +984,8 @@
         }
         // Don't send cancel if authentication succeeds
         mFaceCancelSignal = null;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1028,14 +1074,16 @@
         @Override
         public void run() {
             mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
         }
     };
 
     private void onFaceCancelNotReceived() {
         mLogger.e("Face cancellation not received, transitioning to STOPPED");
         mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
     }
 
     private void handleFaceError(int msgId, final String originalErrMsg) {
@@ -1058,7 +1106,8 @@
         if (msgId == FaceManager.FACE_ERROR_CANCELED
                 && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
             setFaceRunningState(BIOMETRIC_STATE_STOPPED);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_TRIGGERED_DURING_CANCELLATION);
         } else {
             setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         }
@@ -1104,7 +1153,8 @@
         final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
 
         mHandler.postDelayed(() -> {
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET);
         }, getBiometricLockoutDelay());
 
         if (changed) {
@@ -1349,7 +1399,8 @@
     @VisibleForTesting
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
         if (mAssistantVisible) {
             requestActiveUnlock(
                     ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.ASSISTANT,
@@ -1527,7 +1578,7 @@
                 @Override
                 public void onUdfpsPointerDown(int sensorId) {
                     mLogger.logUdfpsPointerDown(sensorId);
-                    requestFaceAuth(true);
+                    requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
                 }
 
                 /**
@@ -1711,7 +1762,7 @@
     protected void handleStartedWakingUp() {
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
         Assert.isMainThread();
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_STARTED_WAKING_UP);
         requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1732,7 +1783,7 @@
             }
         }
         mGoingToSleep = true;
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_GOING_TO_SLEEP);
     }
 
     protected void handleFinishedGoingToSleep(int arg1) {
@@ -1745,7 +1796,8 @@
             }
         }
         // This is set specifically to stop face authentication from running.
-        updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
+        updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
+                FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP);
     }
 
     private void handleScreenTurnedOff() {
@@ -1765,9 +1817,10 @@
         }
         if (mIsDreaming) {
             updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-            updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+            updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_DREAM_STARTED);
         } else {
-            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_TRIGGERED_DREAM_STOPPED);
         }
     }
 
@@ -1832,8 +1885,10 @@
             InteractionJankMonitor interactionJankMonitor,
             LatencyTracker latencyTracker,
             ActiveUnlockConfig activeUnlockConfiguration,
-            KeyguardUpdateMonitorLogger logger) {
-        mLogger = logger;
+            KeyguardUpdateMonitorLogger logger,
+            UiEventLogger uiEventLogger,
+            // This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :(
+            Provider<SessionTracker> sessionTrackerProvider) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mTelephonyListenerManager = telephonyListenerManager;
@@ -1851,6 +1906,9 @@
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
         mActiveUnlockConfig = activeUnlockConfiguration;
+        mLogger = logger;
+        mUiEventLogger = uiEventLogger;
+        mSessionTrackerProvider = sessionTrackerProvider;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
 
         mHandler = new Handler(mainLooper) {
@@ -1934,7 +1992,8 @@
                         setAssistantVisible((boolean) msg.obj);
                         break;
                     case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
-                        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+                        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                                FACE_AUTH_UPDATED_FP_AUTHENTICATED);
                         break;
                     case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
                         updateLogoutEnabled();
@@ -2036,15 +2095,17 @@
         mAuthController.addCallback(new AuthController.Callback() {
             @Override
             public void onAllAuthenticatorsRegistered() {
-                mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
+                mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                        FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED));
             }
 
             @Override
             public void onEnrollmentsChanged() {
-                mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
+                mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                        FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED));
             }
         });
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
         if (mFpm != null) {
             mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
         }
@@ -2156,9 +2217,10 @@
         mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED);
     }
 
-    private void updateBiometricListeningState(int action) {
+    private void updateBiometricListeningState(int action,
+            @NonNull FaceAuthUiEvent faceAuthUiEvent) {
         updateFingerprintListeningState(action);
-        updateFaceListeningState(action);
+        updateFaceListeningState(action, faceAuthUiEvent);
     }
 
     private void updateFingerprintListeningState(int action) {
@@ -2214,7 +2276,8 @@
             return;
         }
         mAuthInterruptActive = active;
-        updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD);
         requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "onReach");
     }
 
@@ -2222,25 +2285,27 @@
      * Requests face authentication if we're on a state where it's allowed.
      * This will re-trigger auth in case it fails.
      * @param userInitiatedRequest true if the user explicitly requested face auth
+     * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
+     * invoked.
      */
-    public void requestFaceAuth(boolean userInitiatedRequest) {
+    public void requestFaceAuth(boolean userInitiatedRequest,
+            @FaceAuthApiRequestReason String reason) {
         mLogger.logFaceAuthRequested(userInitiatedRequest);
-        mIsFaceAuthUserRequested |= userInitiatedRequest;
-        updateFaceListeningState(BIOMETRIC_ACTION_START);
+        updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
     }
 
     /**
      * In case face auth is running, cancel it.
      */
     public void cancelFaceAuth() {
-        stopListeningForFace();
+        stopListeningForFace(FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER);
     }
 
     public boolean isFaceScanning() {
         return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
-    private void updateFaceListeningState(int action) {
+    private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
         if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
@@ -2253,17 +2318,21 @@
                 mLogger.v("Ignoring stopListeningForFace()");
                 return;
             }
-            mIsFaceAuthUserRequested = false;
-            stopListeningForFace();
+            stopListeningForFace(faceAuthUiEvent);
         } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) {
             if (action == BIOMETRIC_ACTION_STOP) {
                 mLogger.v("Ignoring startListeningForFace()");
                 return;
             }
-            startListeningForFace();
+            startListeningForFace(faceAuthUiEvent);
         }
     }
 
+    @Nullable
+    private InstanceId getKeyguardSessionId() {
+        return mSessionTrackerProvider.get().getSessionId(SESSION_KEYGUARD);
+    }
+
     /**
      * Initiates active unlock to get the unlock token ready.
      */
@@ -2332,7 +2401,8 @@
     public void setUdfpsBouncerShowing(boolean showing) {
         mUdfpsBouncerShowing = showing;
         if (mUdfpsBouncerShowing) {
-            updateFaceListeningState(BIOMETRIC_ACTION_START);
+            updateFaceListeningState(BIOMETRIC_ACTION_START,
+                    FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN);
             requestActiveUnlock(
                     ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
                     "udfpsBouncer");
@@ -2494,7 +2564,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 +2633,7 @@
                     becauseCannotSkipBouncer,
                     biometricEnabledForUser,
                     mBouncerFullyShown,
+                    mBouncerIsOrWillBeShowing,
                     faceAuthenticated,
                     faceDisabledForUser,
                     mGoingToSleep,
@@ -2644,7 +2715,7 @@
         }
     }
 
-    private void startListeningForFace() {
+    private void startListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
         final int userId = getCurrentUser();
         final boolean unlockPossible = isUnlockWithFacePossible(userId);
         if (mFaceCancelSignal != null) {
@@ -2658,7 +2729,8 @@
             // Waiting for ERROR_CANCELED before requesting auth again
             return;
         }
-        mLogger.logStartedListeningForFace(mFaceRunningState);
+        mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
+        mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
 
         if (unlockPossible) {
             mFaceCancelSignal = new CancellationSignal();
@@ -2742,8 +2814,10 @@
         }
     }
 
-    private void stopListeningForFace() {
+    private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
         mLogger.v("stopListeningForFace()");
+        mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
+        mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
         if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
             if (mFaceCancelSignal != null) {
                 mFaceCancelSignal.cancel();
@@ -3072,7 +3146,8 @@
                 cb.onKeyguardVisibilityChangedRaw(showing);
             }
         }
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
     }
 
     /**
@@ -3080,7 +3155,8 @@
      */
     private void handleKeyguardReset() {
         mLogger.d("handleKeyguardReset");
-        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_KEYGUARD_RESET);
         mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
     }
 
@@ -3131,7 +3207,8 @@
                     cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
                 }
             }
-            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN);
         }
 
         if (wasBouncerFullyShown != mBouncerFullyShown) {
@@ -3146,7 +3223,8 @@
                     cb.onKeyguardBouncerFullyShowingChanged(mBouncerFullyShown);
                 }
             }
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN);
         }
     }
 
@@ -3274,7 +3352,8 @@
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post if first
         mHandler.post(() -> {
-            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_UPDATED_USER_SWITCHING);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index d718a24..2bc98f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -262,10 +262,18 @@
         logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
     }
 
-    fun logStartedListeningForFace(faceRunningState: Int) {
-        logBuffer.log(TAG, VERBOSE,
-                { int1 = faceRunningState },
-                { "startListeningForFace(): $int1" })
+    fun logStartedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
+        logBuffer.log(TAG, VERBOSE, {
+            int1 = faceRunningState
+            str1 = faceAuthReason
+        }, { "startListeningForFace(): $int1, reason: $str1" })
+    }
+
+    fun logStoppedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
+        logBuffer.log(TAG, VERBOSE, {
+            int1 = faceRunningState
+            str1 = faceAuthReason
+        }, { "stopListeningForFace(): currentFaceRunningState: $int1, reason: $str1" })
     }
 
     fun logSubInfo(subInfo: SubscriptionInfo?) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index d1589b2..7f2680b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.dagger.BiometricsBackground;
@@ -852,7 +853,9 @@
             playStartHaptic();
 
             if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
-                mKeyguardUpdateMonitor.requestFaceAuth(/* userInitiatedRequest */ false);
+                mKeyguardUpdateMonitor.requestFaceAuth(
+                        /* userInitiatedRequest */ false,
+                        FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
             }
         }
         mOnFingerDown = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index d96476f..49e378e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -99,11 +99,12 @@
         mProgressColor = context.getColor(R.color.udfps_enroll_progress);
         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
         mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
-        mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         if (!mIsAccessibilityEnabled) {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+            mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         } else {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
+            mOnFirstBucketFailedColor = mHelpColor;
         }
         mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
         mCheckmarkDrawable.mutate();
@@ -166,8 +167,7 @@
     }
 
     private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
-        if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps
-                && mShowingHelp == showingHelp) {
+        if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
             return;
         }
 
@@ -197,7 +197,6 @@
             }
         }
 
-        mShowingHelp = showingHelp;
         mRemainingSteps = remainingSteps;
         mTotalSteps = totalSteps;
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 91e75f6..822f8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -33,6 +33,12 @@
     fun hide()
 
     /**
+     * Returns the preferred activity to start, depending on if the user has favorited any
+     * controls.
+     */
+    fun resolveActivity(): Class<*>
+
+    /**
      * Request all open dialogs be closed. Set [immediately] to true to dismiss without
      * animations.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index c1e99f5..bf7d716 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -149,6 +149,19 @@
         }
     }
 
+    override fun resolveActivity(): Class<*> {
+        val allStructures = controlsController.get().getFavorites()
+        val selectedStructure = getPreferredStructure(allStructures)
+
+        return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
+            ControlsActivity::class.java
+        } else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
+            ControlsProviderSelectorActivity::class.java
+        } else {
+            ControlsActivity::class.java
+        }
+    }
+
     override fun show(
         parent: ViewGroup,
         onDismiss: Runnable,
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/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/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 05b3420..c65bd9b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
 import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -42,7 +41,6 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 
@@ -55,8 +53,7 @@
     statusBarStateController: StatusBarStateController,
     activityStarter: ActivityStarter,
     qsLogger: QSLogger,
-    private val controlsComponent: ControlsComponent,
-    private val keyguardStateController: KeyguardStateController
+    private val controlsComponent: ControlsComponent
 ) : QSTileImpl<QSTile.State>(
         host,
         backgroundLooper,
@@ -105,7 +102,8 @@
         }
 
         val intent = Intent().apply {
-            component = ComponentName(mContext, ControlsActivity::class.java)
+            component = ComponentName(mContext, controlsComponent.getControlsUiController().get()
+                .resolveActivity())
             addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
             putExtra(ControlsUiController.EXTRA_ANIMATE, true)
         }
@@ -127,10 +125,15 @@
         state.icon = icon
         if (controlsComponent.isEnabled() && hasControlsApps.get()) {
             if (controlsComponent.getVisibility() == AVAILABLE) {
-                val structure = controlsComponent
-                    .getControlsController().get().getPreferredStructure().structure
-                state.state = Tile.STATE_ACTIVE
-                state.secondaryLabel = if (structure == tileLabel) null else structure
+                val structureInfo = controlsComponent
+                    .getControlsController().get().getPreferredStructure()
+                state.state = if (structureInfo.controls.isEmpty()) {
+                    Tile.STATE_INACTIVE
+                } else {
+                    Tile.STATE_ACTIVE
+                }
+                val label = structureInfo.structure
+                state.secondaryLabel = if (label == tileLabel) null else label
             } else {
                 state.state = Tile.STATE_INACTIVE
                 state.secondaryLabel = mContext.getText(R.string.controls_tile_locked)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d5ba07a..91c6a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -97,6 +97,7 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.ActiveUnlockConfig;
+import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
@@ -2351,7 +2352,7 @@
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
         if (height == 0) {
-            mCentralSurfaces.requestFaceAuth(false);
+            mCentralSurfaces.requestFaceAuth(false, FaceAuthApiRequestReason.QS_EXPANDED);
         }
     }
 
@@ -3528,7 +3529,8 @@
                             && !mUpdateMonitor.isFaceDetectionRunning()
                             && !mUpdateMonitor.getUserCanSkipBouncer(
                             KeyguardUpdateMonitor.getCurrentUser())) {
-                        mUpdateMonitor.requestFaceAuth(true);
+                        mUpdateMonitor.requestFaceAuth(true,
+                                FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
                     } else {
                         mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
                                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
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/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 74d8f1b..d3343df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -2,7 +2,6 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
 import android.animation.ValueAnimator
 import android.content.Context
 import android.content.res.Configuration
@@ -880,15 +879,22 @@
         child.actualHeight = (child.collapsedHeight + rubberband).toInt()
     }
 
-    private fun cancelChildExpansion(child: ExpandableView) {
+    @VisibleForTesting
+    fun cancelChildExpansion(
+            child: ExpandableView,
+            animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS
+    ) {
         if (child.actualHeight == child.collapsedHeight) {
             expandCallback.setUserLockedChild(child, false)
             return
         }
-        val anim = ObjectAnimator.ofInt(child, "actualHeight",
-                child.actualHeight, child.collapsedHeight)
+        val anim = ValueAnimator.ofInt(child.actualHeight, child.collapsedHeight)
         anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
-        anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS
+        anim.duration = animationDuration
+        anim.addUpdateListener { animation: ValueAnimator ->
+            // don't use reflection, because the `actualHeight` field may be obfuscated
+            child.actualHeight = animation.animatedValue as Int
+        }
         anim.addListener(object : AnimatorListenerAdapter() {
             override fun onAnimationEnd(animation: Animator) {
                 expandCallback.setUserLockedChild(child, false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 6302ef1..bbff046 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -18,7 +18,7 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
 import android.content.Context
 import android.content.res.Configuration
 import android.os.PowerManager
@@ -28,6 +28,7 @@
 import android.view.MotionEvent
 import android.view.VelocityTracker
 import android.view.ViewConfiguration
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
@@ -276,15 +277,22 @@
         }
     }
 
-    private fun reset(child: ExpandableView) {
+    @VisibleForTesting
+    fun reset(
+            child: ExpandableView,
+            animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+    ) {
         if (child.actualHeight == child.collapsedHeight) {
             setUserLocked(child, false)
             return
         }
-        val anim = ObjectAnimator.ofInt(child, "actualHeight",
-                child.actualHeight, child.collapsedHeight)
+        val anim = ValueAnimator.ofInt(child.actualHeight, child.collapsedHeight)
         anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
-        anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+        anim.duration = animationDuration
+        anim.addUpdateListener { animation: ValueAnimator ->
+            // don't use reflection, because the `actualHeight` field may be obfuscated
+            child.actualHeight = animation.animatedValue as Int
+        }
         anim.addListener(object : AnimatorListenerAdapter() {
             override fun onAnimationEnd(animation: Animator) {
                 setUserLocked(child, false)
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/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 353355b..eeed070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -163,7 +163,7 @@
                 // which is more expensive.
                 if (shelfState.hidden) {
                     // When the shelf is hidden, it won't clip views, so we don't hide rows
-                    return;
+                    continue;
                 }
 
                 final float shelfTop = shelfState.yTranslation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index d1b2974..20b6d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -40,6 +40,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.RegisterStatusBarResult;
+import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.RemoteTransitionAdapter;
@@ -225,7 +226,12 @@
 
     boolean isShadeDisabled();
 
-    void requestFaceAuth(boolean userInitiatedRequest);
+    /**
+     * Request face auth to initiated
+     * @param userInitiatedRequest Whether this was a user initiated request
+     * @param reason Reason why face auth was triggered.
+     */
+    void requestFaceAuth(boolean userInitiatedRequest, @FaceAuthApiRequestReason String reason);
 
     @Override
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index df6e545..6426aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -120,6 +120,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.RegisterStatusBarResult;
+import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
@@ -1612,9 +1613,11 @@
      * Asks {@link KeyguardUpdateMonitor} to run face auth.
      */
     @Override
-    public void requestFaceAuth(boolean userInitiatedRequest) {
+    public void requestFaceAuth(boolean userInitiatedRequest,
+            @FaceAuthApiRequestReason String reason) {
         if (!mKeyguardStateController.canDismissLockScreen()) {
-            mKeyguardUpdateMonitor.requestFaceAuth(userInitiatedRequest);
+            mKeyguardUpdateMonitor.requestFaceAuth(
+                    userInitiatedRequest, reason);
         }
     }
 
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/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 3f8e97f1..b58dbe2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -22,6 +22,7 @@
 import android.hardware.TriggerEvent
 import android.hardware.TriggerEventListener
 import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.FaceAuthApiRequestReason
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.CoreStartable
@@ -71,7 +72,10 @@
             // Not listening anymore since trigger events unregister themselves
             isListening = false
             updateListeningState()
-            keyguardUpdateMonitor.requestFaceAuth(true)
+            keyguardUpdateMonitor.requestFaceAuth(
+                true,
+                FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
+            )
             keyguardUpdateMonitor.requestActiveUnlock(
                 ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE,
                 "KeyguardLiftController")
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/src/com/android/systemui/volume/VolumePanelDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
index 2c74fb9..87a167b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
@@ -52,6 +52,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.settingslib.media.MediaOutputConstants;
 import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.util.ArrayList;
@@ -69,6 +70,7 @@
     private static final int DURATION_SLICE_BINDING_TIMEOUT_MS = 200;
     private static final int DEFAULT_SLICE_SIZE = 4;
 
+    private final ActivityStarter mActivityStarter;
     private RecyclerView mVolumePanelSlices;
     private VolumePanelSlicesAdapter mVolumePanelSlicesAdapter;
     private final LifecycleRegistry mLifecycleRegistry;
@@ -78,8 +80,10 @@
     private boolean mSlicesReadyToLoad;
     private LocalBluetoothProfileManager mProfileManager;
 
-    public VolumePanelDialog(Context context, boolean aboveStatusBar) {
+    public VolumePanelDialog(Context context,
+            ActivityStarter activityStarter, boolean aboveStatusBar) {
         super(context);
+        mActivityStarter = activityStarter;
         mLifecycleRegistry = new LifecycleRegistry(this);
         if (!aboveStatusBar) {
             getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
@@ -100,9 +104,11 @@
         doneButton.setOnClickListener(v -> dismiss());
         Button settingsButton = dialogView.findViewById(R.id.settings_button);
         settingsButton.setOnClickListener(v -> {
-            getContext().startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(
-                    Intent.FLAG_ACTIVITY_NEW_TASK));
             dismiss();
+
+            Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mActivityStarter.startActivity(intent, /* dismissShade= */ true);
         });
 
         LocalBluetoothManager localBluetoothManager = LocalBluetoothManager.getInstance(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
index c2fafbf..0debe0e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
@@ -21,6 +21,7 @@
 import android.view.View
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
 
 private const val TAG = "VolumePanelFactory"
@@ -33,6 +34,7 @@
 @SysUISingleton
 class VolumePanelFactory @Inject constructor(
     private val context: Context,
+    private val activityStarter: ActivityStarter,
     private val dialogLaunchAnimator: DialogLaunchAnimator
 ) {
     companion object {
@@ -45,7 +47,7 @@
             return
         }
 
-        val dialog = VolumePanelDialog(context, aboveStatusBar)
+        val dialog = VolumePanelDialog(context, activityStarter, aboveStatusBar)
         volumePanelDialog = dialog
 
         // Show the dialog.
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt
new file mode 100644
index 0000000..68d0f41
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.keyguard
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert
+import kotlin.reflect.full.declaredMembers
+import org.junit.Test
+
+@SmallTest
+class FaceAuthReasonTest : SysuiTestCase() {
+    @Test
+    fun testApiReasonToUiEvent_forAllReasons_isNotNull() {
+        val declaredMemberProperties = FaceAuthApiRequestReason.Companion::class.declaredMembers
+
+        declaredMemberProperties.forEach {
+            val reason = it.call()
+            try {
+                apiRequestReasonToUiEvent(reason as String)
+            } catch (e: Exception) {
+                Assert.fail(
+                    "Expected the reason: \"$reason\" to have a corresponding FaceAuthUiEvent"
+                )
+            }
+        }
+    }
+}
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..763a63e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -85,6 +86,8 @@
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.ILockSettings;
@@ -95,6 +98,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -189,6 +193,10 @@
     private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
     @Mock
     private IActivityManager mActivityService;
+    @Mock
+    private SessionTracker mSessionTracker;
+    @Mock
+    private UiEventLogger mUiEventLogger;
 
     private final int mCurrentUserId = 100;
     private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -211,6 +219,7 @@
     private MockitoSession mMockitoSession;
     private StatusBarStateController.StateListener mStatusBarStateListener;
     private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
+    private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
 
     @Before
     public void setup() throws RemoteException {
@@ -224,6 +233,7 @@
         when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
         when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
+        when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
 
         // IBiometricsFace@1.0 does not support detection, only authentication.
         when(mFaceSensorProperties.isEmpty()).thenReturn(false);
@@ -655,7 +665,8 @@
         // Stop scanning when bouncer becomes visible
         setKeyguardBouncerVisibility(true);
         clearInvocations(mFaceManager);
-        mKeyguardUpdateMonitor.requestFaceAuth(true);
+        mKeyguardUpdateMonitor.requestFaceAuth(true,
+                FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
     }
@@ -1499,10 +1510,13 @@
         keyguardIsVisible();
 
         mTestableLooper.processAllMessages();
+        clearInvocations(mUiEventLogger);
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
 
-        mKeyguardUpdateMonitor.requestFaceAuth(true);
+        mKeyguardUpdateMonitor.requestFaceAuth(true,
+                FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+
         verify(mFaceManager).authenticate(any(),
                 mCancellationSignalCaptor.capture(),
                 mAuthenticationCallbackCaptor.capture(),
@@ -1511,7 +1525,7 @@
                 anyBoolean());
         CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue();
 
-        bouncerFullyVisible();
+        bouncerWillBeVisibleSoon();
         mTestableLooper.processAllMessages();
 
         assertThat(cancelSignal.isCanceled()).isTrue();
@@ -1594,7 +1608,7 @@
     }
 
     private void triggerSuccessfulFaceAuth() {
-        mKeyguardUpdateMonitor.requestFaceAuth(true);
+        mKeyguardUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
         verify(mFaceManager).authenticate(any(),
                 any(),
                 mAuthenticationCallbackCaptor.capture(),
@@ -1669,6 +1683,11 @@
         setKeyguardBouncerVisibility(true);
     }
 
+    private void bouncerWillBeVisibleSoon() {
+        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, false);
+        mTestableLooper.processAllMessages();
+    }
+
     private void setKeyguardBouncerVisibility(boolean isVisible) {
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
         mTestableLooper.processAllMessages();
@@ -1709,7 +1728,7 @@
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
                     mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
-                    mKeyguardUpdateMonitorLogger);
+                    mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
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/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index ed98881..f7b9438e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -33,17 +33,18 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.plugins.ActivityStarter
 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.QSTileImpl
-import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
@@ -57,13 +58,13 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
 import java.util.Optional
 
 @SmallTest
@@ -93,8 +94,6 @@
     private lateinit var serviceInfo: ControlsServiceInfo
     @Mock
     private lateinit var uiEventLogger: UiEventLogger
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
     @Captor
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -119,7 +118,6 @@
         `when`(qsHost.context).thenReturn(spiedContext)
         `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(controlsComponent.isEnabled()).thenReturn(true)
-        `when`(keyguardStateController.isUnlocked()).thenReturn(true)
         `when`(controlsController.getPreferredStructure())
                 .thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
         secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
@@ -222,12 +220,19 @@
     }
 
     @Test
-    fun testStateAvailableIfListings() {
+    fun testStateActiveIfListingsHasControlsFavorited() {
         verify(controlsListingController).observe(
                 any(LifecycleOwner::class.java),
                 capture(listingCallbackCaptor)
         )
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
+        `when`(controlsController.getPreferredStructure()).thenReturn(
+            StructureInfo(
+                ComponentName("pkg", "cls"),
+                "structure",
+                listOf(ControlInfo("id", "title", "subtitle", 1))
+            )
+        )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -236,6 +241,22 @@
     }
 
     @Test
+    fun testStateInactiveIfListingsHasNoControlsFavorited() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
+        `when`(controlsController.getPreferredStructure())
+                .thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
+    }
+
+    @Test
     fun testStateInactiveIfLocked() {
         verify(controlsListingController).observe(
             any(LifecycleOwner::class.java),
@@ -281,7 +302,14 @@
                 capture(listingCallbackCaptor)
         )
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
-        `when`(keyguardStateController.isUnlocked).thenReturn(true)
+        `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
+        `when`(controlsController.getPreferredStructure()).thenReturn(
+            StructureInfo(
+                ComponentName("pkg", "cls"),
+                "structure",
+                listOf(ControlInfo("id", "title", "subtitle", 1))
+            )
+        )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -305,7 +333,14 @@
         )
         `when`(controlsComponent.getVisibility())
             .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
-        `when`(keyguardStateController.isUnlocked).thenReturn(false)
+        `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
+        `when`(controlsController.getPreferredStructure()).thenReturn(
+            StructureInfo(
+                ComponentName("pkg", "cls"),
+                "structure",
+                listOf(ControlInfo("id", "title", "subtitle", 1))
+            )
+        )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -345,8 +380,7 @@
                 statusBarStateController,
                 activityStarter,
                 qsLogger,
-                controlsComponent,
-                keyguardStateController
+                controlsComponent
         ).also {
             it.initialize()
             testableLooper.processAllMessages()
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/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
new file mode 100644
index 0000000..d925d0a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.ExpandHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DragDownHelperTest : SysuiTestCase() {
+
+    private lateinit var dragDownHelper: DragDownHelper
+
+    private val collapsedHeight = 300
+    private val falsingManager: FalsingManager = mock()
+    private val falsingCollector: FalsingCollector = mock()
+    private val dragDownloadCallback: LockscreenShadeTransitionController = mock()
+    private val expandableView: ExpandableView = mock()
+    private val expandCallback: ExpandHelper.Callback = mock()
+
+    @Before
+    fun setUp() {
+        whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
+
+        dragDownHelper = DragDownHelper(
+                falsingManager,
+                falsingCollector,
+                dragDownloadCallback,
+                mContext
+        ).also {
+            it.expandCallback = expandCallback
+        }
+    }
+
+    @Test
+    fun cancelChildExpansion_updateHeight() {
+        whenever(expandableView.actualHeight).thenReturn(500)
+
+        dragDownHelper.cancelChildExpansion(expandableView, animationDuration = 0)
+
+        verify(expandableView, atLeast(1)).actualHeight = collapsedHeight
+    }
+
+    @Test
+    fun cancelChildExpansion_dontUpdateHeight() {
+        whenever(expandableView.actualHeight).thenReturn(collapsedHeight)
+
+        dragDownHelper.cancelChildExpansion(expandableView, animationDuration = 0)
+
+        verify(expandableView, never()).actualHeight = anyInt()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
new file mode 100644
index 0000000..44cbe51
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class PulseExpansionHandlerTest : SysuiTestCase() {
+
+    private lateinit var pulseExpansionHandler: PulseExpansionHandler
+
+    private val collapsedHeight = 300
+    private val wakeUpCoordinator: NotificationWakeUpCoordinator = mock()
+    private val bypassController: KeyguardBypassController = mock()
+    private val headsUpManager: HeadsUpManagerPhone = mock()
+    private val roundnessManager: NotificationRoundnessManager = mock()
+    private val configurationController: ConfigurationController = mock()
+    private val statusBarStateController: StatusBarStateController = mock()
+    private val falsingManager: FalsingManager = mock()
+    private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock()
+    private val falsingCollector: FalsingCollector = mock()
+    private val dumpManager: DumpManager = mock()
+    private val expandableView: ExpandableView = mock()
+
+    @Before
+    fun setUp() {
+        whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
+
+        pulseExpansionHandler = PulseExpansionHandler(
+                mContext,
+                wakeUpCoordinator,
+                bypassController,
+                headsUpManager,
+                roundnessManager,
+                configurationController,
+                statusBarStateController,
+                falsingManager,
+                lockscreenShadeTransitionController,
+                falsingCollector,
+                dumpManager
+        )
+    }
+
+    @Test
+    fun resetChild_updateHeight() {
+        whenever(expandableView.actualHeight).thenReturn(500)
+
+        pulseExpansionHandler.reset(expandableView, animationDuration = 0)
+
+        verify(expandableView, atLeast(1)).actualHeight = collapsedHeight
+    }
+
+    @Test
+    fun resetChild_dontUpdateHeight() {
+        whenever(expandableView.actualHeight).thenReturn(collapsedHeight)
+
+        pulseExpansionHandler.reset(expandableView, animationDuration = 0)
+
+        verify(expandableView, never()).actualHeight = anyInt()
+    }
+}
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/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 35d2363b..24eff5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -179,7 +179,28 @@
     }
 
     @Test
-    fun resetViewStates_hiddenShelf_viewAlphaDoesNotChange() {
+    fun resetViewStates_hiddenShelf_allRowsBecomesTransparent() {
+        hostView.removeAllViews()
+        val row1 = mockExpandableNotificationRow()
+        hostView.addView(row1)
+        val row2 = mockExpandableNotificationRow()
+        hostView.addView(row2)
+
+        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.hideAmount = 0.25f
+        notificationShelf.viewState.hidden = true
+        ambientState.shelf = notificationShelf
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        val expected = 1f - ambientState.hideAmount
+        assertThat(row1.viewState.alpha).isEqualTo(expected)
+        assertThat(row2.viewState.alpha).isEqualTo(expected)
+    }
+
+    @Test
+    fun resetViewStates_hiddenShelf_shelfAlphaDoesNotChange() {
         val expected = notificationShelf.viewState.alpha
         notificationShelf.viewState.hidden = true
         ambientState.shelf = notificationShelf
@@ -459,3 +480,9 @@
         assertEquals(1f, currentRoundness)
     }
 }
+
+private fun mockExpandableNotificationRow(): ExpandableNotificationRow {
+    return mock(ExpandableNotificationRow::class.java).apply {
+        whenever(viewState).thenReturn(ExpandableViewState())
+    }
+}
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/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 593a63c..fc628cf 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -250,7 +250,9 @@
         // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
         // aware that the virtual display has a secure window on top.
         if ((windowFlags & FLAG_SECURE) != 0) {
-            mSecureWindowCallback.onSecureWindowShown(mDisplayId, activityInfo.applicationInfo.uid);
+            // Post callback on the main thread, so it doesn't block activity launching.
+            mHandler.post(() -> mSecureWindowCallback.onSecureWindowShown(mDisplayId,
+                    activityInfo.applicationInfo.uid));
         }
 
         if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) {
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ef9b2ed..d4b760f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -148,6 +148,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_SHORT_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_STARTER_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_TOP_RESUMED_ACTIVITY;
+import static com.android.server.wm.ActivityTaskManagerService.DUMP_VISIBLE_ACTIVITIES;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString;
 
@@ -9546,7 +9547,8 @@
                     || DUMP_LASTANR_CMD.equals(cmd) || DUMP_LASTANR_TRACES_CMD.equals(cmd)
                     || DUMP_STARTER_CMD.equals(cmd) || DUMP_CONTAINERS_CMD.equals(cmd)
                     || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)
-                    || DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
+                    || DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)
+                    || DUMP_VISIBLE_ACTIVITIES.equals(cmd)) {
                 mAtmInternal.dump(
                         cmd, fd, pw, args, opti, true /* dumpAll */, dumpClient, dumpPackage);
             } else if ("binder-proxies".equals(cmd)) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 98238cc..fd607c3 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,7 @@
                 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 */);
-                    }
-                }
-                // cleanupVpnStateLocked() is called from mVpnRunner.exit()
-                mVpnRunner.exit();
+                stopVpnRunnerAndNotifyAppLocked(mPackage);
             }
 
             try {
@@ -2887,6 +2885,9 @@
                 final LinkProperties lp;
 
                 synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
                     mInterface = interfaceName;
                     mConfig.mtu = maxMtu;
                     mConfig.interfaze = mInterface;
@@ -2988,6 +2989,9 @@
 
             try {
                 synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
                     mConfig.underlyingNetworks = new Network[] {network};
                     mNetworkCapabilities =
                             new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3077,7 +3081,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 +3271,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 +3312,9 @@
             cancelHandleNetworkLostTimeout();
 
             synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+
                 if (exception instanceof IkeProtocolException) {
                     final IkeProtocolException ikeException = (IkeProtocolException) exception;
 
@@ -3420,6 +3435,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).
@@ -4074,6 +4092,29 @@
         }
     }
 
+    @GuardedBy("this")
+    private void stopVpnRunnerAndNotifyAppLocked(@NonNull String packageName) {
+        // 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(packageName)) {
+            intent = buildVpnManagerEventIntent(
+                    VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+                    -1 /* errorClass */, -1 /* errorCode*/, packageName,
+                    getSessionKeyLocked(), makeVpnProfileStateLocked(),
+                    null /* underlyingNetwork */, null /* nc */, null /* lp */);
+        }
+        // cleanupVpnStateLocked() is called from mVpnRunner.exit()
+        mVpnRunner.exit();
+        if (intent != null && isVpnApp(packageName)) {
+            notifyVpnManagerVpnStopped(packageName, ownerUid, intent);
+        }
+    }
+
     /**
      * Stops an already running VPN Profile for the given package.
      *
@@ -4090,7 +4131,21 @@
         // 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);
+            stopVpnRunnerAndNotifyAppLocked(packageName);
+        }
+    }
+
+    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/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f2bcf5e..0b20683 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -280,6 +280,7 @@
 
     private final Object mLock = new Object();
     private final Object mNonPersistentUsersLock = new Object();
+    private final Object mWtfLock = new Object();
 
     private static List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0);
 
@@ -444,10 +445,10 @@
     @interface ShortcutOperation {
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mWtfLock")
     private int mWtfCount = 0;
 
-    @GuardedBy("mLock")
+    @GuardedBy("mWtfLock")
     private Exception mLastWtfStacktrace;
 
     @GuardedBy("mLock")
@@ -4727,13 +4728,15 @@
 
                 mStatLogger.dump(pw, "  ");
 
-                pw.println();
-                pw.print("  #Failures: ");
-                pw.println(mWtfCount);
+                synchronized (mWtfLock) {
+                    pw.println();
+                    pw.print("  #Failures: ");
+                    pw.println(mWtfCount);
 
-                if (mLastWtfStacktrace != null) {
-                    pw.print("  Last failure stack trace: ");
-                    pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+                    if (mLastWtfStacktrace != null) {
+                        pw.print("  Last failure stack trace: ");
+                        pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+                    }
                 }
 
                 pw.println();
@@ -5148,7 +5151,7 @@
         if (e == null) {
             e = new RuntimeException("Stacktrace");
         }
-        synchronized (mLock) {
+        synchronized (mWtfLock) {
             mWtfCount++;
             mLastWtfStacktrace = new Exception("Last failure was logged here:");
         }
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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index fc3320e..027d485 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -203,6 +203,10 @@
     private TaskFragment mAddingToTaskFragment;
     @VisibleForTesting
     boolean mAddingToTask;
+    // Activity that was moved to the top of its task in situations where activity-order changes
+    // due to launch flags (eg. REORDER_TO_TOP).
+    @VisibleForTesting
+    ActivityRecord mMovedToTopActivity;
 
     private ActivityInfo mNewTaskInfo;
     private Intent mNewTaskIntent;
@@ -1763,7 +1767,9 @@
             // The activity is started new rather than just brought forward, so record it as an
             // existence change.
             transitionController.collectExistenceChange(started);
-        } else if (result == START_DELIVERED_TO_TOP && newTransition != null) {
+        } else if (result == START_DELIVERED_TO_TOP && newTransition != null
+                // An activity has changed order/visibility so this isn't just deliver-to-top
+                && mMovedToTopActivity == null) {
             // We just delivered to top, so there isn't an actual transition here.
             if (!forceTransientTransition) {
                 newTransition.abort();
@@ -2343,10 +2349,15 @@
             // In this situation we want to remove all activities from the task up to the one
             // being started. In most cases this means we are resetting the task to its initial
             // state.
+            int[] finishCount = new int[1];
             final ActivityRecord clearTop = targetTask.performClearTop(mStartActivity,
-                    mLaunchFlags);
+                    mLaunchFlags, finishCount);
 
             if (clearTop != null && !clearTop.finishing) {
+                if (finishCount[0] > 0) {
+                    // Only record if actually moved to top.
+                    mMovedToTopActivity = clearTop;
+                }
                 if (clearTop.isRootOfTask()) {
                     // Activity aliases may mean we use different intents for the top activity,
                     // so make sure the task now has the identity of the new intent.
@@ -2383,7 +2394,11 @@
                             mStartActivity.mUserId);
             if (act != null) {
                 final Task task = act.getTask();
-                task.moveActivityToFrontLocked(act);
+                boolean actuallyMoved = task.moveActivityToFrontLocked(act);
+                if (actuallyMoved) {
+                    // Only record if the activity actually moved.
+                    mMovedToTopActivity = act;
+                }
                 act.updateOptionsLocked(mOptions);
                 deliverNewIntent(act, intentGrants);
                 act.getTaskFragment().clearLastPausedActivity();
@@ -3018,10 +3033,7 @@
                 newParent = candidateTf;
             }
         }
-        if (newParent.canHaveEmbeddingActivityTransition(mStartActivity)) {
-            // Make sure the embedded TaskFragment is included in the start activity transition.
-            newParent.collectEmbeddedTaskFragmentIfNeeded();
-        }
+        newParent.mTransitionController.collect(newParent);
         if (mStartActivity.getTaskFragment() == null
                 || mStartActivity.getTaskFragment() == newParent) {
             newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7e310b4..e993496 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -331,6 +331,7 @@
     public static final String DUMP_RECENTS_CMD = "recents";
     public static final String DUMP_RECENTS_SHORT_CMD = "r";
     public static final String DUMP_TOP_RESUMED_ACTIVITY = "top-resumed";
+    public static final String DUMP_VISIBLE_ACTIVITIES = "visible";
 
     /** This activity is not being relaunched, or being relaunched for a non-resize reason. */
     public static final int RELAUNCH_REASON_NONE = 0;
@@ -4054,6 +4055,25 @@
         }
     }
 
+    void dumpVisibleActivitiesLocked(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER VISIBLE ACTIVITIES (dumpsys activity visible)");
+        ArrayList<ActivityRecord> activities =
+                mRootWindowContainer.getDumpActivities("all", /* dumpVisibleRootTasksOnly */ true,
+                        /* dumpFocusedRootTaskOnly */ false, UserHandle.USER_ALL);
+        boolean needSeparator = false;
+        for (int i = activities.size() - 1; i >= 0; i--) {
+            ActivityRecord activity = activities.get(i);
+            if (!activity.isVisible()) {
+                continue;
+            }
+            if (needSeparator) {
+                pw.println();
+            }
+            activity.dump(pw, "", true);
+            needSeparator = true;
+        }
+    }
+
     void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
         dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage,
@@ -6286,6 +6306,8 @@
                     }
                 } else if (DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
                     dumpTopResumedActivityLocked(pw);
+                } else if (DUMP_VISIBLE_ACTIVITIES.equals(cmd)) {
+                    dumpVisibleActivitiesLocked(pw);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index fe7cb19..1636d9f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2184,9 +2184,7 @@
         }
         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;
+        return true;
     }
 
     DecorInsets.Info getDecorInsetsInfo(int rotation, int w, int h) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9186eb2..436cc4d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1399,13 +1399,15 @@
 
     /**
      * Reorder the history task so that the passed activity is brought to the front.
+     * @return whether it was actually moved (vs already being top).
      */
-    final void moveActivityToFrontLocked(ActivityRecord newTop) {
+    final boolean moveActivityToFrontLocked(ActivityRecord newTop) {
         ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing and adding activity %s to root task at top "
                 + "callers=%s", newTop, Debug.getCallers(4));
-
+        int origDist = getDistanceFromTop(newTop);
         positionChildAtTop(newTop);
         updateEffectiveIntent();
+        return getDistanceFromTop(newTop) != origDist;
     }
 
     @Override
@@ -1613,14 +1615,14 @@
         }
     }
 
-    ActivityRecord performClearTop(ActivityRecord newR, int launchFlags) {
+    ActivityRecord performClearTop(ActivityRecord newR, int launchFlags, int[] finishCount) {
         // The task should be preserved for putting new activity in case the last activity is
         // finished if it is normal launch mode and not single top ("clear-task-top").
         mReuseTask = true;
         mTaskSupervisor.beginDeferResume();
         final ActivityRecord result;
         try {
-            result = clearTopActivities(newR, launchFlags);
+            result = clearTopActivities(newR, launchFlags, finishCount);
         } finally {
             mTaskSupervisor.endDeferResume();
             mReuseTask = false;
@@ -1636,14 +1638,19 @@
      * activities on top of it and return the instance.
      *
      * @param newR Description of the new activity being started.
+     * @param finishCount 1-element array that will be populated with the number of activities
+     *                    that have been finished.
      * @return Returns the existing activity in the task that performs the clear-top operation,
      * or {@code null} if none was found.
      */
-    private ActivityRecord clearTopActivities(ActivityRecord newR, int launchFlags) {
+    private ActivityRecord clearTopActivities(ActivityRecord newR, int launchFlags,
+            int[] finishCount) {
         final ActivityRecord r = findActivityInHistory(newR.mActivityComponent, newR.mUserId);
         if (r == null) return null;
 
-        final PooledPredicate f = PooledLambda.obtainPredicate(Task::finishActivityAbove,
+        final PooledPredicate f = PooledLambda.obtainPredicate(
+                (ActivityRecord ar, ActivityRecord boundaryActivity) ->
+                        finishActivityAbove(ar, boundaryActivity, finishCount),
                 PooledLambda.__(ActivityRecord.class), r);
         forAllActivities(f);
         f.recycle();
@@ -1661,7 +1668,8 @@
         return r;
     }
 
-    private static boolean finishActivityAbove(ActivityRecord r, ActivityRecord boundaryActivity) {
+    private static boolean finishActivityAbove(ActivityRecord r, ActivityRecord boundaryActivity,
+            @NonNull int[] finishCount) {
         // Stop operation once we reach the boundary activity.
         if (r == boundaryActivity) return true;
 
@@ -1672,6 +1680,7 @@
                 // TODO: Why is this updating the boundary activity vs. the current activity???
                 boundaryActivity.updateOptionsLocked(opts);
             }
+            finishCount[0] += 1;
             r.finishIfPossible("clear-task-stack", false /* oomAdj */);
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 01f1a0d..036a292 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2275,19 +2275,33 @@
 
         super.onConfigurationChanged(newParentConfig);
 
-        if (shouldStartChangeTransition(mTmpPrevBounds)) {
+        final boolean shouldStartChangeTransition = shouldStartChangeTransition(mTmpPrevBounds);
+        if (shouldStartChangeTransition) {
             initializeChangeTransition(mTmpPrevBounds);
-        } else if (mTaskFragmentOrganizer != null) {
-            // Update the surface here instead of in the organizer so that we can make sure
-            // it can be synced with the surface freezer.
-            final SurfaceControl.Transaction t = getSyncTransaction();
-            updateSurfacePosition(t);
-            updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
+        }
+        if (mTaskFragmentOrganizer != null) {
+            if (mTransitionController.isShellTransitionsEnabled()
+                    && !mTransitionController.isCollecting(this)) {
+                // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
+                // update the surface here if it is not collected by Shell transition.
+                updateOrganizedTaskFragmentSurface();
+            } else if (!mTransitionController.isShellTransitionsEnabled()
+                    && !shouldStartChangeTransition) {
+                // Update the surface here instead of in the organizer so that we can make sure
+                // it can be synced with the surface freezer for legacy app transition.
+                updateOrganizedTaskFragmentSurface();
+            }
         }
 
         sendTaskFragmentInfoChanged();
     }
 
+    private void updateOrganizedTaskFragmentSurface() {
+        final SurfaceControl.Transaction t = getSyncTransaction();
+        updateSurfacePosition(t);
+        updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
+    }
+
     /** Updates the surface size so that the sub windows cannot be shown out of bounds. */
     private void updateOrganizedTaskFragmentSurfaceSize(SurfaceControl.Transaction t,
             boolean forceUpdate) {
@@ -2342,33 +2356,16 @@
                 || endBounds.height() != startBounds.height();
     }
 
-    boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
-        if (!isOrganizedTaskFragment() || !mTransitionController.isShellTransitionsEnabled()) {
-            return false;
-        }
-        // The activity should request open transition when it is becoming visible.
-        return child.isVisibleRequested();
-    }
-
-    void collectEmbeddedTaskFragmentIfNeeded() {
-        if (!isOrganizedTaskFragment() || mTransitionController.isCollecting(this)) {
-            return;
-        }
-        if (getChildCount() == 0) {
-            // The TaskFragment is new created, and just becoming non-empty.
-            mTransitionController.collectExistenceChange(this);
-        } else {
-            mTransitionController.collect(this);
-        }
+    @Override
+    boolean isSyncFinished() {
+        return super.isSyncFinished() && isReadyToTransit();
     }
 
     @Override
     void setSurfaceControl(SurfaceControl sc) {
         super.setSurfaceControl(sc);
         if (mTaskFragmentOrganizer != null) {
-            final SurfaceControl.Transaction t = getSyncTransaction();
-            updateSurfacePosition(t);
-            updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
+            updateOrganizedTaskFragmentSurface();
             // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
             // emit the callbacks now.
             sendTaskFragmentAppeared();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 8e389d3..135fcd4 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;
@@ -92,11 +93,13 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.inputmethod.InputMethodManagerInternal;
+import com.android.server.wm.utils.RotationAnimationUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
 
 /**
@@ -288,9 +291,9 @@
         if (mContainerFreezer == null) {
             mContainerFreezer = new ScreenshotFreezer();
         }
-        mIsSeamlessRotation = true;
         final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow();
         if (top != null) {
+            mIsSeamlessRotation = true;
             top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s "
                     + "because seamless rotating", top.getName());
@@ -706,7 +709,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.
@@ -728,6 +735,10 @@
                 if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
                     // Legacy dispatch relies on this (for now).
                     ar.mEnteringAnimation = visibleAtTransitionEnd;
+                } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
+                        && ar.isVisible()) {
+                    // Transient launch was committed, so report enteringAnimation
+                    ar.mEnteringAnimation = true;
                 }
                 continue;
             }
@@ -1630,6 +1641,9 @@
                 change.setEndAbsBounds(bounds);
             }
             change.setRotation(info.mRotation, endRotation);
+            if (info.mSnapshot != null) {
+                change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
+            }
 
             out.addChange(change);
         }
@@ -1785,6 +1799,10 @@
         /** These are just extra info. They aren't used for change-detection. */
         @Flag int mFlags = FLAG_NONE;
 
+        /** Snapshot surface and luma, if relevant. */
+        SurfaceControl mSnapshot;
+        float mSnapshotLuma;
+
         ChangeInfo(@NonNull WindowContainer origState) {
             mVisible = origState.isVisibleRequested();
             mWindowingMode = origState.getWindowingMode();
@@ -2093,8 +2111,8 @@
      */
     @VisibleForTesting
     private class ScreenshotFreezer implements IContainerFreezer {
-        /** Values are the screenshot "surfaces" or null if it was frozen via BLAST override. */
-        private final ArrayMap<WindowContainer, SurfaceControl> mSnapshots = new ArrayMap<>();
+        /** Keeps track of which windows are frozen. Not all frozen windows have snapshots. */
+        private final ArraySet<WindowContainer> mFrozen = new ArraySet<>();
 
         /** Takes a screenshot and puts it at the top of the container's surface. */
         @Override
@@ -2104,7 +2122,7 @@
             // Check if any parents have already been "frozen". If so, `wc` is already part of that
             // snapshot, so just skip it.
             for (WindowContainer p = wc; p != null; p = p.getParent()) {
-                if (mSnapshots.containsKey(p)) return false;
+                if (mFrozen.contains(p)) return false;
             }
 
             if (mIsSeamlessRotation) {
@@ -2113,7 +2131,7 @@
                 if (top != null && (top == wc || top.isDescendantOf(wc))) {
                     // Don't use screenshots for seamless windows: these will use BLAST even if not
                     // BLAST mode.
-                    mSnapshots.put(wc, null);
+                    mFrozen.add(wc);
                     return true;
                 }
             }
@@ -2146,7 +2164,15 @@
                     .setCallsite("Transition.ScreenshotSync")
                     .setBLASTLayer()
                     .build();
-            mSnapshots.put(wc, snapshotSurface);
+            mFrozen.add(wc);
+            final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
+            changeInfo.mSnapshot = snapshotSurface;
+            if (wc.asDisplayContent() != null) {
+                // This isn't cheap, so only do it for rotations: assume display-level is rotate
+                // since most of the time it is.
+                changeInfo.mSnapshotLuma = RotationAnimationUtils.getMedianBorderLuma(
+                        screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace());
+            }
             SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
 
             t.setBuffer(snapshotSurface, buffer);
@@ -2167,11 +2193,12 @@
 
         @Override
         public void cleanUp(SurfaceControl.Transaction t) {
-            for (int i = 0; i < mSnapshots.size(); ++i) {
-                SurfaceControl snap = mSnapshots.valueAt(i);
+            for (int i = 0; i < mFrozen.size(); ++i) {
+                SurfaceControl snap =
+                        Objects.requireNonNull(mChanges.get(mFrozen.valueAt(i))).mSnapshot;
                 // May be null if it was frozen via BLAST override.
                 if (snap == null) continue;
-                t.remove(snap);
+                t.reparent(snap, null /* newParent */);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 23928ae..0d6532f 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -227,6 +227,14 @@
     }
 
     /**
+     * @return the collecting transition. {@code null} if there is no collecting transition.
+     */
+    @Nullable
+    Transition getCollectingTransition() {
+        return mCollectingTransition;
+    }
+
+    /**
      * @return the collecting transition sync Id. This should only be called when there is a
      * collecting transition.
      */
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 92e52de..979a6e5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1834,6 +1834,11 @@
         return null;
     }
 
+    int getDistanceFromTop(WindowContainer child) {
+        int idx = mChildren.indexOf(child);
+        return idx < 0 ? -1 : mChildren.size() - 1 - idx;
+    }
+
     private ActivityRecord processGetActivityWithBoundary(Predicate<ActivityRecord> callback,
             WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
             boolean[] boundaryFound, WindowContainer wc) {
@@ -2827,7 +2832,6 @@
     void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
         if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
             mDisplayContent.mTransitionController.collectVisibleChange(this);
-            // TODO(b/207070762): request shell transition for activityEmbedding change.
             return;
         }
         mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index d34ad7d..9456f0f 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,7 +21,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -416,7 +415,10 @@
 
             if (!shouldApplyIndependently) {
                 // Although there is an active sync, we want to apply the transaction now.
-                if (!mTransitionController.isCollecting()) {
+                // TODO(b/232042367) Redesign the organizer update on activity callback so that we
+                // we will know about the transition explicitly.
+                final Transition transition = mTransitionController.getCollectingTransition();
+                if (transition == null) {
                     // This should rarely happen, and we should try to avoid using
                     // {@link #applySyncTransaction} with Shell transition.
                     // We still want to apply and merge the transaction to the active sync
@@ -426,8 +428,7 @@
                                     + " because there is an ongoing sync for"
                                     + " applySyncTransaction().");
                 }
-                // TODO(b/207070762) make sure changes are all collected.
-                applyTransaction(wct, -1 /* syncId */, null /* transition */, caller);
+                applyTransaction(wct, -1 /* syncId */, transition, caller);
                 return;
             }
 
@@ -821,7 +822,8 @@
             case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: {
                 final TaskFragmentCreationParams taskFragmentCreationOptions =
                         hop.getTaskFragmentCreationOptions();
-                createTaskFragment(taskFragmentCreationOptions, errorCallbackToken, caller);
+                createTaskFragment(taskFragmentCreationOptions, errorCallbackToken, caller,
+                        transition);
                 break;
             }
             case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: {
@@ -848,7 +850,8 @@
                         break;
                     }
                 }
-                effects |= deleteTaskFragment(taskFragment, organizer, errorCallbackToken);
+                effects |= deleteTaskFragment(taskFragment, organizer, errorCallbackToken,
+                        transition);
                 break;
             }
             case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
@@ -922,8 +925,15 @@
                     break;
                 }
 
-                prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(parent,
-                        activity);
+                if (transition != null) {
+                    transition.collect(activity);
+                    if (activity.getParent() != null) {
+                        // Collect the current parent. Its visibility may change as a result of
+                        // this reparenting.
+                        transition.collect(activity.getParent());
+                    }
+                    transition.collect(parent);
+                }
                 activity.reparent(parent, POSITION_TOP);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
@@ -1118,7 +1128,7 @@
                     break;
                 }
                 reparentTaskFragment(oldParent.asTaskFragment(), newParent, organizer,
-                        errorCallbackToken);
+                        errorCallbackToken, transition);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
             }
@@ -1162,41 +1172,6 @@
         return effects;
     }
 
-    private void prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(
-            @NonNull TaskFragment taskFragment, @NonNull ActivityRecord activity) {
-        if (!taskFragment.canHaveEmbeddingActivityTransition(activity)) {
-            return;
-        }
-
-        // The reparent can happen in the following cases:
-        // 1. Reparent an existing activity to split when app launches new intent.
-        //    - This happens after app calls to start activity, but before the activity is actually
-        //      started, so we don't expect any collecting transition, but if it does, we can't
-        //      queue the WCT because the start activity won't wait.
-        // 2. Reparent an existing activity to split to launch placeholder when Task size changed.
-        //    - We expect to have a collecting transition for the Task resize, so just collect.
-        // 3. Reparent a new launching activity to an always-expand container.
-        // 4. Reparent a new launching activity to split to launch placeholder together.
-        // 5. Reparent a new launching activity to an existing split.
-        //    - The new launching activity should have start an OPEN transition, so just collect.
-        // 6. Reparent PiP activity back to the original Task.
-        //    - This should be part of the exiting PiP transition, so just collect.
-
-        if (!taskFragment.getBounds().equals(activity.getBounds()) && activity.isVisible()
-                && !mTransitionController.isCollecting()) {
-            // 1. Reparent an existing activity to split when app launches new intent.
-            mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE, activity);
-        }
-
-        // We expect the activity to be in the transition already, so just collect the TaskFragment.
-        if (mTransitionController.isCollecting(activity)) {
-            taskFragment.collectEmbeddedTaskFragmentIfNeeded();
-        } else {
-            ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Reparenting Activity"
-                    + " to embedded TaskFragment, but the Activity is not collected");
-        }
-    }
-
     /** A helper method to send minimum dimension violation error to the client. */
     private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
             IBinder errorCallbackToken, String reason) {
@@ -1773,8 +1748,9 @@
         }
     }
 
-    void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
-            @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) {
+    private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
+            @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller,
+            @Nullable Transition transition) {
         final ActivityRecord ownerActivity =
                 ActivityRecord.forTokenLocked(creationParams.getOwnerToken());
         final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface(
@@ -1829,11 +1805,13 @@
         taskFragment.setWindowingMode(creationParams.getWindowingMode());
         taskFragment.setBounds(creationParams.getInitialBounds());
         mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
+
+        if (transition != null) transition.collectExistenceChange(taskFragment);
     }
 
-    void reparentTaskFragment(@NonNull TaskFragment oldParent,
+    private void reparentTaskFragment(@NonNull TaskFragment oldParent,
             @Nullable WindowContainer<?> newParent, @Nullable ITaskFragmentOrganizer organizer,
-            @Nullable IBinder errorCallbackToken) {
+            @Nullable IBinder errorCallbackToken, @Nullable Transition transition) {
         final TaskFragment newParentTF;
         if (newParent == null) {
             // Use the old parent's parent if the caller doesn't specify the new parent.
@@ -1875,13 +1853,24 @@
                     HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
             return;
         }
+        if (transition != null) {
+            // Collect the current parent. It's visibility may change as a result of this
+            // reparenting.
+            transition.collect(oldParent);
+            transition.collect(newParentTF);
+        }
         while (oldParent.hasChild()) {
-            oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP);
+            final WindowContainer child = oldParent.getChildAt(0);
+            if (transition != null) {
+                transition.collect(child);
+            }
+            child.reparent(newParentTF, POSITION_TOP);
         }
     }
 
     private int deleteTaskFragment(@NonNull TaskFragment taskFragment,
-            @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) {
+            @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken,
+            @Nullable Transition transition) {
         final int index = mLaunchTaskFragments.indexOfValue(taskFragment);
         if (index < 0) {
             final Throwable exception =
@@ -1901,6 +1890,9 @@
                     HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT, exception);
             return 0;
         }
+
+        if (transition != null) transition.collectExistenceChange(taskFragment);
+
         mLaunchTaskFragments.removeAt(index);
         taskFragment.remove(true /* withTransition */, "deleteTaskFragment");
         return TRANSACT_EFFECTS_LIFECYCLE;
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/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index c78bc59..00be7ed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -35,6 +35,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
 import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
@@ -66,6 +67,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -1402,6 +1404,39 @@
                 canEmbedActivity(taskFragment, starting, task));
     }
 
+    @Test
+    public void testRecordActivityMovementBeforeDeliverToTop() {
+        final Task task = new TaskBuilder(mAtm.mTaskSupervisor).build();
+        final ActivityRecord activityBot = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
+
+        activityBot.setVisible(false);
+        activityBot.mVisibleRequested = false;
+
+        assertTrue(activityTop.isVisible());
+        assertTrue(activityTop.mVisibleRequested);
+
+        final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
+                        | FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
+        starter.mStartActivity = activityBot;
+        task.inRecents = true;
+        starter.setInTask(task);
+        starter.getIntent().setComponent(activityBot.mActivityComponent);
+        final int result = starter.setReason("testRecordActivityMovement").execute();
+
+        assertEquals(START_DELIVERED_TO_TOP, result);
+        assertNotNull(starter.mMovedToTopActivity);
+
+        final ActivityStarter starter2 = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
+                        | FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
+        starter2.setInTask(task);
+        starter2.getIntent().setComponent(activityBot.mActivityComponent);
+        final int result2 = starter2.setReason("testRecordActivityMovement").execute();
+
+        assertEquals(START_DELIVERED_TO_TOP, result2);
+        assertNull(starter2.mMovedToTopActivity);
+    }
+
     private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
             ActivityRecord source, ActivityOptions options, Task inTask,
             TaskFragment inTaskFragment) {
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 f322779..9480ae8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -263,7 +263,8 @@
         // Detach from process so the activities can be removed from hierarchy when finishing.
         activity1.detachFromProcess();
         activity2.detachFromProcess();
-        assertTrue(task.performClearTop(activity1, 0 /* launchFlags */).finishing);
+        int[] finishCount = new int[1];
+        assertTrue(task.performClearTop(activity1, 0 /* launchFlags */, finishCount).finishing);
         assertFalse(task.hasChild());
         // In real case, the task should be preserved for adding new activity.
         assertTrue(task.isAttached());
@@ -277,7 +278,7 @@
         doReturn(true).when(activityB).shouldBeVisibleUnchecked();
         doReturn(true).when(activityC).shouldBeVisibleUnchecked();
         activityA.getConfiguration().densityDpi += 100;
-        assertTrue(task.performClearTop(activityA, 0 /* launchFlags */).finishing);
+        assertTrue(task.performClearTop(activityA, 0 /* launchFlags */, finishCount).finishing);
         // The bottom activity should destroy directly without relaunch for config change.
         assertEquals(ActivityRecord.State.DESTROYING, activityA.getState());
         verify(activityA, never()).startRelaunching();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 4f68e98..8f186e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -27,6 +27,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -82,6 +83,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
@@ -962,8 +964,17 @@
     @Test
     public void testTransientLaunch() {
         final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
+        final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>();
         final TransitionController controller = new TransitionController(mAtm, snapshotController,
-                mock(TransitionTracer.class));
+                mock(TransitionTracer.class)) {
+            @Override
+            protected void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
+                if (ar.mEnteringAnimation) {
+                    enteringAnimReports.add(ar);
+                }
+                super.dispatchLegacyAppTransitionFinished(ar);
+            }
+        };
         final ITransitionPlayer player = new ITransitionPlayer.Default();
         controller.registerTransitionPlayer(player, null /* playerProc */);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
@@ -1008,6 +1019,7 @@
 
         activity1.mVisibleRequested = false;
         activity2.mVisibleRequested = true;
+        activity2.setVisible(true);
 
         // Using abort to force-finish the sync (since we obviously can't wait for drawing).
         // We didn't call abort on the actual transition, so it will still run onTransactionReady
@@ -1018,9 +1030,11 @@
         // called until finish).
         verify(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false));
 
+        enteringAnimReports.clear();
         closeTransition.finishTransition();
 
         verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
+        assertTrue(enteringAnimReports.contains(activity2));
     }
 
     @Test
@@ -1167,6 +1181,42 @@
         assertTrue(mSyncEngine.isReady(transition.getSyncId()));
     }
 
+    @Test
+    public void testVisibleChange_snapshot() {
+        registerTestTransitionPlayer();
+        final ActivityRecord app = createActivityRecord(mDisplayContent);
+        final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
+                app.mTransitionController, mWm.mSyncEngine);
+        app.mTransitionController.moveToCollecting(transition, BLASTSyncEngine.METHOD_NONE);
+        final SurfaceControl mockSnapshot = mock(SurfaceControl.class);
+        transition.setContainerFreezer(new Transition.IContainerFreezer() {
+            @Override
+            public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
+                Objects.requireNonNull(transition.mChanges.get(wc)).mSnapshot = mockSnapshot;
+                return true;
+            }
+
+            @Override
+            public void cleanUp(SurfaceControl.Transaction t) {
+            }
+        });
+        final Task task = app.getTask();
+        transition.collect(task);
+        final Rect bounds = new Rect(task.getBounds());
+        Configuration c = new Configuration(task.getRequestedOverrideConfiguration());
+        bounds.inset(10, 10);
+        c.windowConfiguration.setBounds(bounds);
+        task.onRequestedOverrideConfigurationChanged(c);
+
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                transition.mParticipants, transition.mChanges);
+        TransitionInfo info = Transition.calculateTransitionInfo(
+                TRANSIT_CHANGE, 0, targets, transition.mChanges, mMockT);
+        assertEquals(mockSnapshot,
+                info.getChange(task.mRemoteToken.toWindowContainerToken()).getSnapshot());
+        transition.abort();
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
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;
+    }
+}