Merge changes I1bada812,Ia50e462d

* changes:
  Automatically run LocaleManager CTS cases for relevant changes.
  Automatically run LocaleManager CTS cases for relevant changes.
diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS
index 69dfcc9..062ffd4 100644
--- a/cmds/idmap2/OWNERS
+++ b/cmds/idmap2/OWNERS
@@ -1,4 +1,4 @@
 set noparent
 toddke@google.com
-rtmitchell@google.com
-patb@google.com
\ No newline at end of file
+patb@google.com
+zyy@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index 7370af3..44af059 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31636,6 +31636,8 @@
     method @Nullable public double[] createDoubleArray();
     method @Nullable public float[] createFloatArray();
     method @Nullable public int[] createIntArray();
+    method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>);
+    method @Nullable public <T extends android.os.IInterface> java.util.ArrayList<T> createInterfaceArrayList(@NonNull java.util.function.Function<android.os.IBinder,T>);
     method @Nullable public long[] createLongArray();
     method @Nullable public String[] createStringArray();
     method @Nullable public java.util.ArrayList<java.lang.String> createStringArrayList();
@@ -31676,6 +31678,8 @@
     method @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
     method public int readInt();
     method public void readIntArray(@NonNull int[]);
+    method public <T extends android.os.IInterface> void readInterfaceArray(@NonNull T[], @NonNull java.util.function.Function<android.os.IBinder,T>);
+    method public <T extends android.os.IInterface> void readInterfaceList(@NonNull java.util.List<T>, @NonNull java.util.function.Function<android.os.IBinder,T>);
     method public void readList(@NonNull java.util.List, @Nullable ClassLoader);
     method public <T> void readList(@NonNull java.util.List<? super T>, @Nullable ClassLoader, @NonNull Class<T>);
     method public long readLong();
@@ -31728,6 +31732,8 @@
     method public void writeFloatArray(@Nullable float[]);
     method public void writeInt(int);
     method public void writeIntArray(@Nullable int[]);
+    method public <T extends android.os.IInterface> void writeInterfaceArray(@Nullable T[]);
+    method public <T extends android.os.IInterface> void writeInterfaceList(@Nullable java.util.List<T>);
     method public void writeInterfaceToken(@NonNull String);
     method public void writeList(@Nullable java.util.List);
     method public void writeLong(long);
@@ -40972,11 +40978,11 @@
   }
 
   public class CarrierConfigManager {
-    method @Nullable public android.os.PersistableBundle getConfig();
-    method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
-    method @Nullable public android.os.PersistableBundle getConfigForSubId(int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfig();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigForSubId(int);
     method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
-    method public void notifyConfigChangedForSubId(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int);
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
     field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index da9fd3e..591fe55 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -114,7 +114,7 @@
     method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
     method public static void resumeAppSwitches() throws android.os.RemoteException;
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopBackgroundUsersOnSwitch(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopUserOnSwitch(int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -129,9 +129,9 @@
     field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
-    field public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1; // 0xffffffff
-    field public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0; // 0x0
-    field public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1; // 0x1
+    field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
+    field public static final int STOP_USER_ON_SWITCH_FALSE = 0; // 0x0
+    field public static final int STOP_USER_ON_SWITCH_TRUE = 1; // 0x1
   }
 
   public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable {
@@ -1198,7 +1198,7 @@
 
   public final class DisplayManager {
     method public boolean areUserDisabledHdrTypesAllowed();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearUserPreferredDisplayMode();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
     method @NonNull public int[] getUserDisabledHdrTypes();
     method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
     method public boolean isMinimalPostProcessingRequested(int);
@@ -1206,7 +1206,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
     field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
     field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d77227b..d7ce3b8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4087,45 +4087,46 @@
      * @hide
      */
     @TestApi
-    public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1;
+    public static final int STOP_USER_ON_SWITCH_DEFAULT = -1;
 
     /**
-     * Overrides value defined by the platform and stop background users on switch.
+     * Overrides value defined by the platform and stop user on switch.
      *
      * @hide
      */
     @TestApi
-    public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1;
+    public static final int STOP_USER_ON_SWITCH_TRUE = 1;
 
     /**
-     * Overrides value defined by the platform and don't stop background users on switch.
+     * Overrides value defined by the platform and don't stop user on switch.
      *
      * @hide
      */
     @TestApi
-    public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0;
+    public static final int STOP_USER_ON_SWITCH_FALSE = 0;
 
     /** @hide */
-    @IntDef(prefix = { "STOP_BG_USERS_ON_SWITCH_" }, value = {
-            STOP_BG_USERS_ON_SWITCH_DEFAULT,
-            STOP_BG_USERS_ON_SWITCH_TRUE,
-            STOP_BG_USERS_ON_SWITCH_FALSE
+    @IntDef(prefix = { "STOP_USER_ON_SWITCH_" }, value = {
+            STOP_USER_ON_SWITCH_DEFAULT,
+            STOP_USER_ON_SWITCH_TRUE,
+            STOP_USER_ON_SWITCH_FALSE
     })
-    public @interface StopBgUsersOnSwitch {}
+    public @interface StopUserOnSwitch {}
 
     /**
-     * Sets whether background users should be stopped when the current user is switched.
+     * Sets whether the current foreground user (and its profiles) should be stopped after switched
+     * out.
      *
-     * <p>Should only be used on tests.
+     * <p>Should only be used on tests. Doesn't apply to {@link UserHandle#SYSTEM system user}.
      *
      * @hide
      */
     @TestApi
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
-    public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) {
+    public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
         try {
-            getService().setStopBackgroundUsersOnSwitch(value);
+            getService().setStopUserOnSwitch(value);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7dbcd5f..324e1ae 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -16,7 +16,7 @@
 
 package android.app;
 
-import static android.app.ActivityManager.StopBgUsersOnSwitch;
+import static android.app.ActivityManager.StopUserOnSwitch;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -688,9 +688,10 @@
             @Nullable VoiceInteractionManagerProvider provider);
 
     /**
-     * Sets whether background users should be stopped when the current user is switched.
+     * Sets whether the current foreground user (and its profiles) should be stopped after switched
+     * out.
      */
-    public abstract void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value);
+    public abstract void setStopUserOnSwitch(@StopUserOnSwitch int value);
 
     /**
      * Provides the interface to communicate between voice interaction manager service and
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index de79a64..853d5e8 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -26,6 +26,7 @@
 import android.app.IApplicationThread;
 import android.app.IActivityController;
 import android.app.IAppTask;
+import android.app.IForegroundServiceObserver;
 import android.app.IInstrumentationWatcher;
 import android.app.IProcessObserver;
 import android.app.IServiceConnection;
@@ -279,6 +280,8 @@
     boolean clearApplicationUserData(in String packageName, boolean keepState,
             in IPackageDataObserver observer, int userId);
     void makeServicesNonForeground(in String packageName, int userId);
+    /** Returns {@code false} if the callback could not be registered, {@true} otherwise. */
+    boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback);
     @UnsupportedAppUsage
     void forceStopPackage(in String packageName, int userId);
     boolean killPids(in int[] pids, in String reason, boolean secure);
@@ -349,7 +352,7 @@
     @UnsupportedAppUsage
     boolean switchUser(int userid);
     @UnsupportedAppUsage
-    void setStopBackgroundUsersOnSwitch(int value);
+    void setStopUserOnSwitch(int value);
     boolean removeTask(int taskId);
     @UnsupportedAppUsage
     void registerProcessObserver(in IProcessObserver observer);
diff --git a/core/java/android/app/IForegroundServiceObserver.aidl b/core/java/android/app/IForegroundServiceObserver.aidl
new file mode 100644
index 0000000..2b0cbed
--- /dev/null
+++ b/core/java/android/app/IForegroundServiceObserver.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/**
+ * Notify the client of all changes to services' foreground state.
+ * @param serviceToken unique identifier for a service instance
+ * @param packageName identifies the app hosting the service
+ * @param userId identifies the started user in which the app is running
+ * @param isForeground whether the service is in the "foreground" mode now, i.e.
+ *     whether it is an FGS
+ *
+ * @hide
+ */
+oneway interface IForegroundServiceObserver {
+    void onForegroundStateChanged(in IBinder serviceToken, in String packageName, int userId, boolean isForeground);
+}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 1909f3c..1da0a74 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -15,6 +15,7 @@
 per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IAppTraceRetriever.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IForegroundServiceObserver.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IInstrumentationWatcher.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IntentService.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IServiceConnection.aidl = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 7c2b1b7..8be2b48 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -97,7 +97,8 @@
     AppWidgetProviderInfo mInfo;
     View mView;
     int mViewMode = VIEW_MODE_NOINIT;
-    int mLayoutId = -1;
+    // If true, we should not try to re-apply the RemoteViews on the next inflation.
+    boolean mColorMappingChanged = false;
     private InteractionHandler mInteractionHandler;
     private boolean mOnLightBackground;
     private SizeF mCurrentSize = null;
@@ -540,7 +541,6 @@
                 return;
             }
             content = getDefaultView();
-            mLayoutId = -1;
             mViewMode = VIEW_MODE_DEFAULT;
         } else {
             // Select the remote view we are actually going to apply.
@@ -557,8 +557,7 @@
             // inflate any requested LayoutParams.
             mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
 
-            int layoutId = rvToApply.getLayoutId();
-            if (rvToApply.canRecycleView(mView)) {
+            if (!mColorMappingChanged && rvToApply.canRecycleView(mView)) {
                 try {
                     rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
                             mColorResources);
@@ -583,7 +582,6 @@
                 }
             }
 
-            mLayoutId = layoutId;
             mViewMode = VIEW_MODE_CONTENT;
         }
 
@@ -591,6 +589,7 @@
     }
 
     private void applyContent(View content, boolean recycled, Exception exception) {
+        mColorMappingChanged = false;
         if (content == null) {
             if (mViewMode == VIEW_MODE_ERROR) {
                 // We've already done this -- nothing to do.
@@ -626,7 +625,7 @@
 
         // If our stale view has been prepared to match active, and the new
         // layout matches, try recycling it
-        if (remoteViews.canRecycleView(mView)) {
+        if (!mColorMappingChanged && remoteViews.canRecycleView(mView)) {
             try {
                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
                         mView,
@@ -666,7 +665,6 @@
 
         @Override
         public void onViewApplied(View v) {
-            AppWidgetHostView.this.mLayoutId = mLayoutId;
             mViewMode = VIEW_MODE_CONTENT;
 
             applyContent(v, mIsReapply, null);
@@ -907,7 +905,7 @@
         }
         mColorMapping = colorMapping.clone();
         mColorResources = RemoteViews.ColorResources.create(mContext, mColorMapping);
-        mLayoutId = -1;
+        mColorMappingChanged = true;
         mViewMode = VIEW_MODE_NOINIT;
         reapplyLastRemoteViews();
     }
@@ -937,7 +935,7 @@
         if (mColorResources != null) {
             mColorResources = null;
             mColorMapping = null;
-            mLayoutId = -1;
+            mColorMappingChanged = true;
             mViewMode = VIEW_MODE_NOINIT;
             reapplyLastRemoteViews();
         }
diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS
index 91a0abf..3669817 100644
--- a/core/java/android/content/om/OWNERS
+++ b/core/java/android/content/om/OWNERS
@@ -3,4 +3,4 @@
 toddke@android.com
 toddke@google.com
 patb@google.com
-rtmitchell@google.com
+zyy@google.com
diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS
index bc2355c..d12d920 100644
--- a/core/java/android/content/res/OWNERS
+++ b/core/java/android/content/res/OWNERS
@@ -3,4 +3,4 @@
 toddke@android.com
 toddke@google.com
 patb@google.com
-rtmitchell@google.com
+zyy@google.com
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index c7f3983..4481885 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1119,7 +1119,7 @@
      * @hide
      */
     @TestApi
-    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
     public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) {
         // Create a new object containing default values for the unused fields like mode ID and
         // alternative refresh rates.
@@ -1134,7 +1134,7 @@
      * @hide
      */
     @TestApi
-    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
     public void clearUserPreferredDisplayMode() {
         mGlobal.setUserPreferredDisplayMode(null);
     }
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b3be9da..0d5f1af 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -164,7 +164,7 @@
     int getPreferredWideGamutColorSpaceId();
 
     // Sets the user preferred display mode.
-    // Requires WRITE_SECURE_SETTINGS permission.
+    // Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission.
     void setUserPreferredDisplayMode(in Mode mode);
     Mode getUserPreferredDisplayMode();
 
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d1e6716..5a2f27d 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -62,6 +62,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.function.IntFunction;
 import java.util.function.Supplier;
 
 /**
@@ -178,8 +180,12 @@
  * {@link #writeStrongInterface(IInterface)}, {@link #readStrongBinder()},
  * {@link #writeBinderArray(IBinder[])}, {@link #readBinderArray(IBinder[])},
  * {@link #createBinderArray()},
+ * {@link #writeInterfaceArray(T[])}, {@link #readInterfaceArray(T[], Function)},
+ * {@link #createInterfaceArray(IntFunction, Function)},
  * {@link #writeBinderList(List)}, {@link #readBinderList(List)},
- * {@link #createBinderArrayList()}.</p>
+ * {@link #createBinderArrayList()},
+ * {@link #writeInterfaceList(List)}, {@link #readInterfaceList(List, Function)},
+ * {@link #createInterfaceArrayList(Function)}.</p>
  *
  * <p>FileDescriptor objects, representing raw Linux file descriptor identifiers,
  * can be written and {@link ParcelFileDescriptor} objects returned to operate
@@ -1730,6 +1736,30 @@
     }
 
     /**
+     * Flatten a homogeneous array containing an IInterface type into the parcel,
+     * at the current dataPosition() and growing dataCapacity() if needed.  The
+     * type of the objects in the array must be one that implements IInterface.
+     *
+     * @param val The array of objects to be written.
+     *
+     * @see #createInterfaceArray
+     * @see #readInterfaceArray
+     * @see IInterface
+     */
+    public final <T extends IInterface> void writeInterfaceArray(
+            @SuppressLint("ArrayReturn") @Nullable T[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeStrongInterface(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
      * @hide
      */
     public final void writeCharSequenceArray(@Nullable CharSequence[] val) {
@@ -1785,6 +1815,50 @@
     }
 
     /**
+     * Read and return a new array of T (IInterface) from the parcel.
+     *
+     * @return the IInterface array of type T
+     * @param newArray a function to create an array of T with a given length
+     * @param asInterface a function to convert IBinder object into T (IInterface)
+     */
+    @SuppressLint({"ArrayReturn", "NullableCollection", "SamShouldBeLast"})
+    @Nullable
+    public final <T extends IInterface> T[] createInterfaceArray(
+            @NonNull IntFunction<T[]> newArray, @NonNull Function<IBinder, T> asInterface) {
+        int N = readInt();
+        if (N >= 0) {
+            T[] val = newArray.apply(N);
+            for (int i=0; i<N; i++) {
+                val[i] = asInterface.apply(readStrongBinder());
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Read an array of T (IInterface) from a parcel.
+     *
+     * @param asInterface a function to convert IBinder object into T (IInterface)
+     *
+     * @throws BadParcelableException Throws BadParcelableException if the length of `val`
+     *    mismatches the number of items in the parcel.
+     */
+    public final <T extends IInterface> void readInterfaceArray(
+            @SuppressLint("ArrayReturn") @NonNull T[] val,
+            @NonNull Function<IBinder, T> asInterface) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = asInterface.apply(readStrongBinder());
+            }
+        } else {
+            throw new BadParcelableException("bad array lengths");
+        }
+    }
+
+    /**
      * Flatten a List containing a particular object type into the parcel, at
      * the current dataPosition() and growing dataCapacity() if needed.  The
      * type of the objects in the list must be one that implements Parcelable.
@@ -1898,6 +1972,28 @@
     }
 
     /**
+     * Flatten a {@code List} containing T (IInterface) objects into this parcel
+     * at the current position. They can later be retrieved with
+     * {@link #createInterfaceArrayList} or {@link #readInterfaceList}.
+     *
+     * @see #createInterfaceArrayList
+     * @see #readInterfaceList
+     */
+    public final <T extends IInterface> void writeInterfaceList(@Nullable List<T> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeStrongInterface(val.get(i));
+            i++;
+        }
+    }
+
+    /**
      * Flatten a {@code List} containing arbitrary {@code Parcelable} objects into this parcel
      * at the current position. They can later be retrieved using
      * {@link #readParcelableList(List, ClassLoader)} if required.
@@ -3380,6 +3476,32 @@
     }
 
     /**
+     * Read and return a new ArrayList containing T (IInterface) objects from
+     * the parcel that was written with {@link #writeInterfaceList} at the
+     * current dataPosition().  Returns null if the
+     * previously written list object was null.
+     *
+     * @return A newly created ArrayList containing T (IInterface)
+     *
+     * @see #writeInterfaceList
+     */
+    @SuppressLint({"ConcreteCollection", "NullableCollection"})
+    @Nullable
+    public final <T extends IInterface> ArrayList<T> createInterfaceArrayList(
+            @NonNull Function<IBinder, T> asInterface) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList<T> l = new ArrayList<T>(N);
+        while (N > 0) {
+            l.add(asInterface.apply(readStrongBinder()));
+            N--;
+        }
+        return l;
+    }
+
+    /**
      * Read into the given List items String objects that were written with
      * {@link #writeStringList} at the current dataPosition().
      *
@@ -3422,6 +3544,28 @@
     }
 
     /**
+     * Read into the given List items IInterface objects that were written with
+     * {@link #writeInterfaceList} at the current dataPosition().
+     *
+     * @see #writeInterfaceList
+     */
+    public final <T extends IInterface> void readInterfaceList(@NonNull List<T> list,
+            @NonNull Function<IBinder, T> asInterface) {
+        int M = list.size();
+        int N = readInt();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            list.set(i, asInterface.apply(readStrongBinder()));
+        }
+        for (; i<N; i++) {
+            list.add(asInterface.apply(readStrongBinder()));
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+    }
+
+    /**
      * Read the list of {@code Parcelable} objects at the current data position into the
      * given {@code list}. The contents of the {@code list} are replaced. If the serialized
      * list was {@code null}, {@code list} is cleared.
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 4b12aa6..e863111 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -223,22 +223,32 @@
         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
                 componentId++) {
 
-            final BatteryConsumer.Key key = mData.getKey(componentId, PROCESS_STATE_ANY);
-            final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
-            final long durationMs = getUsageDurationMillis(key);
+            final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
+            for (BatteryConsumer.Key key : keys) {
+                final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
+                final long durationMs = getUsageDurationMillis(key);
 
-            if (powerDeciCoulombs == 0 && durationMs == 0) {
-                // No interesting data. Make sure not to even write the COMPONENT int.
-                continue;
+                if (powerDeciCoulombs == 0 && durationMs == 0) {
+                    // No interesting data. Make sure not to even write the COMPONENT int.
+                    continue;
+                }
+
+                interestingData = true;
+                if (proto == null) {
+                    // We're just asked whether there is data, not to actually write it.
+                    // And there is.
+                    return true;
+                }
+
+                if (key.processState == PROCESS_STATE_ANY) {
+                    writePowerComponentUsage(proto,
+                            BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
+                            componentId, powerDeciCoulombs, durationMs);
+                } else {
+                    writePowerUsageSlice(proto, componentId, powerDeciCoulombs, durationMs,
+                            key.processState);
+                }
             }
-
-            interestingData = true;
-            if (proto == null) {
-                // We're just asked whether there is data, not to actually write it. And there is.
-                return true;
-            }
-
-            writePowerComponent(proto, componentId, powerDeciCoulombs, durationMs);
         }
         for (int idx = 0; idx < mData.layout.customPowerComponentCount; idx++) {
             final int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + idx;
@@ -257,15 +267,49 @@
                 return true;
             }
 
-            writePowerComponent(proto, componentId, powerDeciCoulombs, durationMs);
+            writePowerComponentUsage(proto,
+                    BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
+                    componentId, powerDeciCoulombs, durationMs);
         }
         return interestingData;
     }
 
-    private void writePowerComponent(ProtoOutputStream proto, int componentId,
+    private void writePowerUsageSlice(ProtoOutputStream proto, int componentId,
+            long powerDeciCoulombs, long durationMs, int processState) {
+        final long slicesToken =
+                proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.SLICES);
+        writePowerComponentUsage(proto,
+                BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+                        .POWER_COMPONENT,
+                componentId, powerDeciCoulombs, durationMs);
+
+        final int procState;
+        switch (processState) {
+            case BatteryConsumer.PROCESS_STATE_FOREGROUND:
+                procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+                        .FOREGROUND;
+                break;
+            case BatteryConsumer.PROCESS_STATE_BACKGROUND:
+                procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+                        .BACKGROUND;
+                break;
+            case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
+                procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+                        .FOREGROUND_SERVICE;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown process state: " + processState);
+        }
+
+        proto.write(BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+                .PROCESS_STATE, procState);
+
+        proto.end(slicesToken);
+    }
+
+    private void writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId,
             long powerDeciCoulombs, long durationMs) {
-        final long token =
-                proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS);
+        final long token = proto.start(tag);
         proto.write(
                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
                         .COMPONENT,
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 54b87ab7..dd31e02 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -499,6 +499,13 @@
     public static final String NAMESPACE_SYSTEM_TIME = "system_time";
 
     /**
+     * Namespace for TARE configurations.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_TARE = "tare";
+
+    /**
      * Telephony related properties.
      *
      * @hide
@@ -738,7 +745,7 @@
      * @param name      The name of the property to look up.
      * @param defaultValue The value to return if the property does not exist or has no non-null
      *                     value.
-     * @return the correspondfing value, or defaultValue if none exists.
+     * @return the corresponding value, or defaultValue if none exists.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0348107..295ed77 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16609,30 +16609,6 @@
             public static final String WEAR_PLATFORM_MR_NUMBER = "wear_platform_mr_number";
 
             /**
-             * The bluetooth settings storing duplicate address of companion device.
-             * @hide
-             */
-            public static final String COMPANION_BT_ADDRESS_DUAL = "companion_bt_address_dual";
-
-            /**
-             * The offset of the visible screen from the display bottom (overscan bottom).
-             * @hide
-             */
-            public static final String BOTTOM_OFFSET = "bottom_offset";
-
-            /**
-             * The shape of the display.
-             * @hide
-             */
-            public static final String DISPLAY_SHAPE = "display_shape";
-
-            // Possible display shapes
-            /** @hide */
-            public static final int DISPLAY_SHAPE_SQUARE = 0;
-            /** @hide */
-            public static final int DISPLAY_SHAPE_ROUND = 1;
-
-            /**
              * The different levels of screen brightness the user can select.
              * @hide
              */
@@ -16706,12 +16682,6 @@
             public static final String AMBIENT_PLUGGED_TIMEOUT_MIN = "ambient_plugged_timeout_min";
 
             /**
-             * The companion device's bluetooth address.
-             * @hide
-             */
-            public static final String COMPANION_ADDRESS = "companion_address";
-
-            /**
              * What OS does paired device has.
              * @hide
              */
@@ -16758,12 +16728,6 @@
             public static final int HFP_CLIENT_DISABLED = 2;
 
             /**
-             * The current HFP client profile setting.
-             * @hide
-             */
-            public static final String HFP_CLIENT_PROFILE_ENABLED = "hfp_client_profile_enabled";
-
-            /**
              * The companion phone's android version.
              * @hide
              */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index be15965..c88d4f8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -211,6 +211,7 @@
 import java.io.StringWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -742,6 +743,8 @@
      */
     private long mRtLastAttemptedDrawFrameNum = 0;
 
+    private Consumer<SurfaceControl.Transaction> mBLASTDrawConsumer;
+
     private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
 
     private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
@@ -3259,6 +3262,9 @@
         }
 
         boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
+        if (mBLASTDrawConsumer != null) {
+            useBlastSync = true;
+        }
 
         if (!cancelDraw) {
             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
@@ -3977,6 +3983,9 @@
             Log.d(mTag, "Creating frameCompleteCallback");
         }
 
+        final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer;
+        mBLASTDrawConsumer = null;
+
         mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(() -> {
             long frameNr = mBlastBufferQueue.getLastAcquiredFrameNum();
             if (DEBUG_BLAST) {
@@ -4002,6 +4011,9 @@
             mHandler.postAtFrontOfQueue(() -> {
                 if (useBlastSync) {
                     mSurfaceChangedTransaction.merge(tmpTransaction);
+                    if (blastSyncConsumer != null) {
+                        blastSyncConsumer.accept(mSurfaceChangedTransaction);
+                    }
                 }
 
                 if (reportNextDraw) {
@@ -10457,4 +10469,35 @@
             listener.onBufferTransformHintChanged(hint);
         }
     }
+
+    /**
+     * Redirect the next draw of this ViewRoot (from the UI thread perspective)
+     * to the passed in consumer. This can be used to create P2P synchronization
+     * between ViewRoot's however it comes with many caveats.
+     *
+     * 1. You MUST consume the transaction, by either applying it immediately or
+     *    merging it in to another transaction. The threading model doesn't
+     *    allow you to hold in the passed transaction.
+     * 2. If you merge it in to another transaction, this ViewRootImpl will be
+     *    paused until you finally apply that transaction and it receives
+     *    the callback from SF. If you lose track of the transaction you will
+     *    ANR the app.
+     * 3. Only one person can consume the transaction at a time, if you already
+     *    have a pending consumer for this frame, the function will return false
+     * 4. Someone else may have requested to consume the next frame, in which case
+     *    this function will return false and you will not receive a callback.
+     * 5. This function does not trigger drawing so even if it returns true you
+     *    may not receive a callback unless there is some other UI thread work
+     *    to trigger drawing. If it returns true, and a draw occurs, the callback
+     *    will be called (Though again watch out for the null transaction case!)
+     * 6. This function must be called on the UI thread. The consumer will likewise
+     *    be called on the UI thread.
+     */
+    public boolean consumeNextDraw(Consumer<SurfaceControl.Transaction> consume) {
+       if (mBLASTDrawConsumer != null) {
+           return false;
+       }
+       mBLASTDrawConsumer = consume;
+       return true;
+   }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d76f789..8287de2 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2442,6 +2442,20 @@
         public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
 
         /**
+         * Flag to indicate that this window will be excluded while computing the magnifiable region
+         * on the un-scaled screen coordinate, which could avoid the cutout on the magnification
+         * border. It should be used for unmagnifiable overlays.
+         *
+         * </p><p>
+         * Note unlike {@link #PRIVATE_FLAG_NOT_MAGNIFIABLE}, this flag doesn't affect the ability
+         * of magnification. If you want to the window to be unmagnifiable and doesn't lead to the
+         * cutout, you need to combine both of them.
+         * </p><p>
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000;
+
+        /**
          * Flag to prevent the window from being magnified by the accessibility magnifier.
          *
          * TODO(b/190623172): This is a temporary solution and need to find out another way instead.
@@ -2552,6 +2566,7 @@
                 PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                 PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
@@ -2633,6 +2648,10 @@
                         equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
                         name = "IS_ROUNDED_CORNERS_OVERLAY"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
+                        equals = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
+                        name = "EXCLUDE_FROM_SCREEN_MAGNIFICATION"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_NOT_MAGNIFIABLE,
                         equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
                         name = "NOT_MAGNIFIABLE"),
diff --git a/core/java/android/view/inputmethod/TextSnapshot.java b/core/java/android/view/inputmethod/TextSnapshot.java
index 33ce282..977e6f4 100644
--- a/core/java/android/view/inputmethod/TextSnapshot.java
+++ b/core/java/android/view/inputmethod/TextSnapshot.java
@@ -129,8 +129,8 @@
      * <p>Values may be any combination of the following values:</p>
      * <ul>
      *     <li>{@link android.text.TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS}</li>
-     *     <li>{@link android.text.TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_WORDS}</li>
-     *     <li>{@link android.text.TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_SENTENCES}</li>
+     *     <li>{@link android.text.TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}</li>
+     *     <li>{@link android.text.TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}</li>
      * </ul>
      *
      * <p>You should generally just take a non-zero value to mean "start out in caps mode" though.
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index 165dcdf..a118f9a 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -213,6 +213,7 @@
                 + " isEmpty=" + mIsEmpty
                 + " runningActivityCount=" + mRunningActivityCount
                 + " isVisible=" + mIsVisible
+                + " activities=" + mActivities
                 + " positionInParent=" + mPositionInParent
                 + " isTaskClearedForReuse=" + mIsTaskClearedForReuse
                 + "}";
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index b00148a..4f2e7db 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -30,9 +30,11 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -43,10 +45,10 @@
  * resolve it to an activity.
  */
 public class DisplayResolveInfo implements TargetInfo, Parcelable {
-    // Temporary flag for new chooser delegate behavior. There are occassional token
-    // permission errors from bouncing through the delegate. Watch out before reenabling:
-    // b/157272342 is one example but this issue has been reported many times
-    private static final boolean ENABLE_CHOOSER_DELEGATE = false;
+    private final boolean mEnableChooserDelegate =
+            DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags.USE_DELEGATE_CHOOSER,
+                    false);
 
     private final ResolveInfo mResolveInfo;
     private CharSequence mDisplayLabel;
@@ -178,7 +180,7 @@
 
     @Override
     public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
-        if (ENABLE_CHOOSER_DELEGATE) {
+        if (mEnableChooserDelegate) {
             return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
         } else {
             activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId);
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 1b3fd7b..d1019c5 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -501,6 +501,11 @@
     public static final String IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP =
             "is_nearby_share_first_target_in_ranked_app";
 
+    /**
+     * (boolean) Whether to enable the new unbundled "delegate chooser" implementation.
+     */
+    public static final String USE_DELEGATE_CHOOSER = "use_delegate_chooser";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto
index bcce784..c0a9f03 100644
--- a/core/proto/android/os/batteryusagestats.proto
+++ b/core/proto/android/os/batteryusagestats.proto
@@ -62,6 +62,26 @@
             optional int64 duration_millis = 3;
         }
         repeated PowerComponentUsage power_components = 2;
+
+        // Represents a slice of power attribution, e.g. "cpu while in the background"
+        // or "wifi when running a background service".  Queries that care about
+        // PowerComponentUsage slices need to be aware of all supported dimensions.
+        // There are no roll-ups included in the slices - it is up to the clients
+        // of this data to aggregate values as needed.
+        message PowerComponentUsageSlice {
+            optional PowerComponentUsage power_component = 1;
+
+            enum ProcessState {
+                UNSPECIFIED = 0;
+                FOREGROUND = 1;
+                BACKGROUND = 2;
+                FOREGROUND_SERVICE = 3;
+            }
+
+            optional ProcessState process_state = 2;
+        }
+
+        repeated PowerComponentUsageSlice slices = 3;
     }
 
     // Total power usage for the device during this session.
diff --git a/core/res/res/interpolator/emphasized.xml b/core/res/res/interpolator/emphasized.xml
new file mode 100644
index 0000000..aae939a
--- /dev/null
+++ b/core/res/res/interpolator/emphasized.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData=
+        "M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1"/>
diff --git a/core/res/res/interpolator/emphasized_accelerate.xml b/core/res/res/interpolator/emphasized_accelerate.xml
new file mode 100644
index 0000000..f39faf8
--- /dev/null
+++ b/core/res/res/interpolator/emphasized_accelerate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.3"
+    android:controlY1="0"
+    android:controlX2="0.8"
+    android:controlY2="0.15"/>
diff --git a/core/res/res/interpolator/emphasized_decelerate.xml b/core/res/res/interpolator/emphasized_decelerate.xml
new file mode 100644
index 0000000..3e4fab6
--- /dev/null
+++ b/core/res/res/interpolator/emphasized_decelerate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.05"
+    android:controlY1="0.7"
+    android:controlX2="0.1"
+    android:controlY2="1"/>
diff --git a/core/res/res/interpolator/standard.xml b/core/res/res/interpolator/standard.xml
new file mode 100644
index 0000000..bf61229
--- /dev/null
+++ b/core/res/res/interpolator/standard.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.2"
+    android:controlY1="0"
+    android:controlX2="0"
+    android:controlY2="1"/>
diff --git a/core/res/res/interpolator/standard_accelerate.xml b/core/res/res/interpolator/standard_accelerate.xml
new file mode 100644
index 0000000..68e2fa1
--- /dev/null
+++ b/core/res/res/interpolator/standard_accelerate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.3"
+    android:controlY1="0"
+    android:controlX2="1"
+    android:controlY2="1"/>
diff --git a/core/res/res/interpolator/standard_decelerate.xml b/core/res/res/interpolator/standard_decelerate.xml
new file mode 100644
index 0000000..0169871
--- /dev/null
+++ b/core/res/res/interpolator/standard_decelerate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0"
+    android:controlY1="0"
+    android:controlX2="0"
+    android:controlY2="1"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fceb951..7f68bfd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2723,7 +2723,7 @@
     <!-- Name of the activity that will handle requests to the system to choose an activity for
          the purposes of resolving an intent. -->
     <string name="config_chooserActivity" translatable="false"
-            >com.android.systemui/com.android.systemui.chooser.ChooserActivity</string>
+            >com.android.intentresolver/.ChooserActivity</string>
     <!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of
          the default framework version. If left empty, then the framework version will be used.
          Example: com.google.android.myapp/.resolver.MyResolverActivity  -->
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index 7ff76df..e230a54 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -28,6 +28,7 @@
 
 import android.os.BatteryConsumer;
 import android.os.BatteryUsageStats;
+import android.os.UidBatteryConsumer;
 import android.os.nano.BatteryUsageStatsAtomsProto;
 import android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage;
 
@@ -134,6 +135,43 @@
                         componentProto.durationMillis);
             }
         }
+
+        for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+                componentId++) {
+            final BatteryConsumer.Key[] keys = consumer.getKeys(componentId);
+            if (keys == null || keys.length <= 1) {
+                continue;
+            }
+
+            for (BatteryConsumer.Key key : keys) {
+                if (key.processState == 0) {
+                    continue;
+                }
+
+                BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+                        sliceProto = null;
+                for (BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+                        slice : consumerProto.slices) {
+                    if (slice.powerComponent.component == componentId
+                            && slice.processState == key.processState) {
+                        sliceProto = slice;
+                        break;
+                    }
+                }
+
+                final long expectedPowerDc = convertMahToDc(consumer.getConsumedPower(key));
+                final long expectedUsageDurationMillis = consumer.getUsageDurationMillis(key);
+                if (expectedPowerDc == 0 && expectedUsageDurationMillis == 0) {
+                    assertThat(sliceProto).isNull();
+                } else {
+                    assertThat(sliceProto).isNotNull();
+                    assertThat(sliceProto.powerComponent.powerDeciCoulombs)
+                            .isEqualTo(expectedPowerDc);
+                    assertThat(sliceProto.powerComponent.durationMillis)
+                            .isEqualTo(expectedUsageDurationMillis);
+                }
+            }
+        }
     }
 
     private void assertSameUidBatteryConsumer(
@@ -172,14 +210,17 @@
         final BatteryStatsImpl.Uid batteryStatsUid3 = batteryStats.getUidStatsLocked(UID_3);
 
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"})
+                new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
+                        /* includePowerModels */ true,
+                        /* includeProcessStats */true)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
                         .setStatsStartTimestamp(1000);
-        builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid0)
+        final UidBatteryConsumer.Builder uidBuilder = builder.getOrCreateUidBatteryConsumerBuilder(
+                batteryStatsUid0)
                 .setPackageWithHighestDrain("myPackage0")
-                .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 1000)
-                .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 2000)
+                .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000)
+                .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000)
                 .setConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
                 .setConsumedPower(
@@ -193,6 +234,20 @@
                 .setUsageDurationForCustomComponentMillis(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 800);
 
+        final BatteryConsumer.Key keyFg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        final BatteryConsumer.Key keyBg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+        final BatteryConsumer.Key keyFgs = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        uidBuilder.setConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                .setUsageDurationMillis(keyFg, 8100)
+                .setConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY)
+                .setUsageDurationMillis(keyBg, 8200)
+                .setConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY)
+                .setUsageDurationMillis(keyFgs, 8300);
+
         builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid1)
                 .setPackageWithHighestDrain("myPackage1")
                 .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 1234);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 06e7d14..1e9fda6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -83,4 +83,13 @@
                 && ((SplitPairRule) splitRule).shouldFinishSecondaryWithPrimary();
         return shouldFinishSecondaryWithPrimary || isPlaceholderContainer;
     }
+
+    @Override
+    public String toString() {
+        return "SplitContainer{"
+                + " primaryContainer=" + mPrimaryContainer
+                + " secondaryContainer=" + mSecondaryContainer
+                + " splitRule=" + mSplitRule
+                + "}";
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 20515e7..9014102 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -497,7 +497,7 @@
             return;
         }
         List<SplitInfo> currentSplitStates = getActiveSplitStates();
-        if (mLastReportedSplitStates.equals(currentSplitStates)) {
+        if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
             return;
         }
         mLastReportedSplitStates.clear();
@@ -506,15 +506,19 @@
     }
 
     /**
-     * Returns a list of descriptors for currently active split states.
+     * @return a list of descriptors for currently active split states. If the value returned is
+     * null, that indicates that the active split states are in an intermediate state and should
+     * not be reported.
      */
+    @Nullable
     private List<SplitInfo> getActiveSplitStates() {
         List<SplitInfo> splitStates = new ArrayList<>();
         for (SplitContainer container : mSplitContainers) {
             if (container.getPrimaryContainer().isEmpty()
                     || container.getSecondaryContainer().isEmpty()) {
-                // Skipping containers that do not have any activities to report.
-                continue;
+                // We are in an intermediate state because either the split container is about to be
+                // removed or the primary or secondary container are about to receive an activity.
+                return null;
             }
             ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack();
             ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 80d9c2c..6805fde 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -27,6 +27,7 @@
 import android.window.WindowContainerTransaction;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -267,4 +268,42 @@
             mLastRequestedBounds.set(bounds);
         }
     }
+
+    @Override
+    public String toString() {
+        return toString(true /* includeContainersToFinishOnExit */);
+    }
+
+    /**
+     * @return string for this TaskFragmentContainer and includes containers to finish on exit
+     * based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always
+     * included in the string, then calling {@link #toString()} on a container that mutually
+     * finishes with another container would cause a stack overflow.
+     */
+    private String toString(boolean includeContainersToFinishOnExit) {
+        return "TaskFragmentContainer{"
+                + " token=" + mToken
+                + " info=" + mInfo
+                + " topNonFinishingActivity=" + getTopNonFinishingActivity()
+                + " pendingAppearedActivities=" + mPendingAppearedActivities
+                + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
+                + containersToFinishOnExitToString() : "")
+                + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit
+                + " isFinished=" + mIsFinished
+                + " lastRequestedBounds=" + mLastRequestedBounds
+                + "}";
+    }
+
+    private String containersToFinishOnExitToString() {
+        StringBuilder sb = new StringBuilder("[");
+        Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator();
+        while (containerIterator.hasNext()) {
+            sb.append(containerIterator.next().toString(
+                    false /* includeContainersToFinishOnExit */));
+            if (containerIterator.hasNext()) {
+                sb.append(", ");
+            }
+        }
+        return sb.append("]").toString();
+    }
 }
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
index 7dc2f31..9fe0247 100644
--- a/libs/WindowManager/Shell/res/layout/pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml
@@ -65,28 +65,25 @@
     <LinearLayout
         android:id="@+id/top_end_container"
         android:layout_gravity="top|end"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal">
-
         <ImageButton
             android:id="@+id/settings"
             android:layout_width="@dimen/pip_action_size"
             android:layout_height="@dimen/pip_action_size"
             android:contentDescription="@string/pip_phone_settings"
-            android:layout_gravity="top|start"
             android:gravity="center"
             android:src="@drawable/pip_ic_settings"
             android:background="?android:selectableItemBackgroundBorderless" />
 
         <ImageButton
-            android:id="@+id/enter_split"
+            android:id="@+id/dismiss"
             android:layout_width="@dimen/pip_action_size"
             android:layout_height="@dimen/pip_action_size"
-            android:layout_gravity="top|start"
+            android:contentDescription="@string/pip_phone_close"
             android:gravity="center"
-            android:contentDescription="@string/pip_phone_enter_split"
-            android:src="@drawable/pip_expand"
+            android:src="@drawable/pip_ic_close_white"
             android:background="?android:selectableItemBackgroundBorderless" />
     </LinearLayout>
 
@@ -100,14 +97,4 @@
         android:padding="@dimen/pip_resize_handle_padding"
         android:src="@drawable/pip_resize_handle"
         android:background="?android:selectableItemBackgroundBorderless" />
-
-    <ImageButton
-        android:id="@+id/dismiss"
-        android:layout_width="@dimen/pip_action_size"
-        android:layout_height="@dimen/pip_action_size"
-        android:contentDescription="@string/pip_phone_close"
-        android:layout_gravity="top|end"
-        android:gravity="center"
-        android:src="@drawable/pip_ic_close_white"
-        android:background="?android:selectableItemBackgroundBorderless" />
 </FrameLayout>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index c88fc16..764854a 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -24,9 +24,6 @@
     <!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
     <string name="pip_phone_settings">Settings</string>
 
-    <!-- Label for the PIP enter split button [CHAR LIMIT=NONE] -->
-    <string name="pip_phone_enter_split">Enter split screen</string>
-
     <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
     <string name="pip_menu_title">Menu</string>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 711a0ac..b80dcd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -43,7 +43,6 @@
 import com.android.wm.shell.pip.tv.TvPipMenuController;
 import com.android.wm.shell.pip.tv.TvPipNotificationController;
 import com.android.wm.shell.pip.tv.TvPipTransition;
-import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
@@ -161,14 +160,13 @@
             PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<LegacySplitScreenController> splitScreenOptional,
-            Optional<SplitScreenController> newSplitScreenOptional,
             DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
                 syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
                 tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
-                pipTransitionController, splitScreenOptional, newSplitScreenOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+                pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ec70147..944dfed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -55,7 +55,6 @@
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
 import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
 import com.android.wm.shell.transition.Transitions;
@@ -216,15 +215,14 @@
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             PipTransitionController pipTransitionController,
             Optional<LegacySplitScreenController> splitScreenOptional,
-            Optional<SplitScreenController> newSplitScreenOptional,
             DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
                 syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
                 menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
-                pipTransitionController, splitScreenOptional, newSplitScreenOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+                pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
     }
 
     @WMSingleton
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 6cc5f09..b6e5804 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
@@ -77,7 +77,6 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
@@ -127,8 +126,7 @@
     private final int mExitAnimationDuration;
     private final int mCrossFadeAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
-    private final Optional<SplitScreenController> mSplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -254,8 +252,7 @@
             @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             @NonNull PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreenController> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
+            Optional<LegacySplitScreenController> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -277,7 +274,6 @@
         mPipAnimationController = pipAnimationController;
         mPipUiEventLoggerLogger = pipUiEventLogger;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
-        mLegacySplitScreenOptional = legacySplitScreenOptional;
         mSplitScreenOptional = splitScreenOptional;
         mTaskOrganizer = shellTaskOrganizer;
         mMainExecutor = mainExecutor;
@@ -377,11 +373,8 @@
      *   activity render it's final configuration while the Task is still in PiP.
      * - setWindowingMode to undefined at the end of transition
      * @param animationDurationMs duration in millisecond for the exiting PiP transition
-     * @param requestEnterSplit whether the enterSplit button is pressed on PiP or not.
-     *                             Indicate the user wishes to directly put PiP into split screen
-     *                             mode.
      */
-    public void exitPip(int animationDurationMs, boolean requestEnterSplit) {
+    public void exitPip(int animationDurationMs) {
         if (!mPipTransitionState.isInPip()
                 || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
                 || mToken == null) {
@@ -394,7 +387,7 @@
                 PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final Rect destinationBounds = mPipBoundsState.getDisplayBounds();
-        final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
+        final int direction = syncWithSplitScreenBounds(destinationBounds)
                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
                 : TRANSITION_DIRECTION_LEAVE_PIP;
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
@@ -403,7 +396,7 @@
         // We set to fullscreen here for now, but later it will be set to UNDEFINED for
         // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
         wct.setActivityWindowingMode(mToken,
-                direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && !requestEnterSplit
+                direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
                         ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         : WINDOWING_MODE_FULLSCREEN);
         wct.setBounds(mToken, destinationBounds);
@@ -442,7 +435,7 @@
         wct.setWindowingMode(mToken, getOutPipWindowingMode());
         // Simply reset the activity mode set prior to the animation running.
         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-        mLegacySplitScreenOptional.ifPresent(splitScreen -> {
+        mSplitScreenOptional.ifPresent(splitScreen -> {
             if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
                 wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */);
             }
@@ -1172,7 +1165,6 @@
             @PipAnimationController.TransitionDirection int direction,
             @PipAnimationController.AnimationType int type) {
         final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds());
-        final boolean isPipTopLeft = isPipTopLeft();
         mPipBoundsState.setBounds(destinationBounds);
         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
             removePipImmediately();
@@ -1218,10 +1210,10 @@
                             null /* callback */, false /* withStartDelay */);
                 });
             } else {
-                applyFinishBoundsResize(wct, direction, isPipTopLeft);
+                applyFinishBoundsResize(wct, direction);
             }
         } else {
-            applyFinishBoundsResize(wct, direction, isPipTopLeft);
+            applyFinishBoundsResize(wct, direction);
         }
 
         finishResizeForMenu(destinationBounds);
@@ -1249,11 +1241,7 @@
         } else if (isOutPipDirection(direction)) {
             // If we are animating to fullscreen or split screen, then we need to reset the
             // override bounds on the task to ensure that the task "matches" the parent's bounds.
-            if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
-                taskBounds = destinationBounds;
-            } else {
-                taskBounds = null;
-            }
+            taskBounds = null;
             applyWindowingModeChangeOnExit(wct, direction);
         } else {
             // Just a resize in PIP
@@ -1273,20 +1261,8 @@
      * applying it.
      */
     public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct,
-            @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) {
-        if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
-            mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct);
-        } else {
-            mTaskOrganizer.applyTransaction(wct);
-        }
-    }
-
-    private boolean isPipTopLeft() {
-        final Rect topLeft = new Rect();
-        final Rect bottomRight = new Rect();
-        mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
-
-        return topLeft.contains(mPipBoundsState.getBounds());
+            @PipAnimationController.TransitionDirection int direction) {
+        mTaskOrganizer.applyTransaction(wct);
     }
 
     /**
@@ -1371,27 +1347,18 @@
     }
 
     /**
-     * Sync with {@link LegacySplitScreenController} or {@link SplitScreenController} on destination
-     * bounds if PiP is going to split screen.
+     * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
+     * screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
      * @return {@code true} if destinationBounds is altered for split screen
      */
-    private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
-        if (enterSplit && mSplitScreenOptional.isPresent()) {
-            final Rect topLeft = new Rect();
-            final Rect bottomRight = new Rect();
-            mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
-            final boolean isPipTopLeft = isPipTopLeft();
-            destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
-            return true;
-        }
-
-        if (!mLegacySplitScreenOptional.isPresent()) {
+    private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) {
+        if (!mSplitScreenOptional.isPresent()) {
             return false;
         }
 
-        LegacySplitScreenController legacySplitScreen = mLegacySplitScreenOptional.get();
+        LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
         if (!legacySplitScreen.isDividerVisible()) {
             // fail early if system is not in split screen mode
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 5687f4d..ae8c1b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -95,11 +95,6 @@
          * Called when the PIP requested to show the menu.
          */
         void onPipShowMenu();
-
-        /**
-         * Called when the PIP requested to enter Split.
-         */
-        void onEnterSplit();
     }
 
     private final Matrix mMoveTransform = new Matrix();
@@ -463,10 +458,6 @@
         mListeners.forEach(Listener::onPipDismiss);
     }
 
-    void onEnterSplit() {
-        mListeners.forEach(Listener::onEnterSplit);
-    }
-
     /**
      * @return the best set of actions to show in the PiP menu.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 69ae45d..47a8c67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -151,7 +151,7 @@
                         result = true;
                         break;
                     case AccessibilityNodeInfo.ACTION_EXPAND:
-                        mMotionHelper.expandLeavePip(false /* skipAnimation */);
+                        mMotionHelper.expandLeavePip();
                         result = true;
                         break;
                     default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index aeeb73f..a32eb16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -482,8 +482,7 @@
                     false /* fromShelfAdjustment */,
                     wct /* windowContainerTransaction */);
             if (wct != null) {
-                mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME,
-                        false /* wasPipTopLeft */);
+                mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME);
             }
         };
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
index 0644657..3eeba6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.util.Log;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -32,7 +34,6 @@
     protected ViewGroup mViewRoot;
     protected ViewGroup mTopEndContainer;
     protected View mDragHandle;
-    protected View mEnterSplitButton;
     protected View mSettingsButton;
     protected View mDismissButton;
 
@@ -43,13 +44,14 @@
      * Bind the necessary views.
      */
     public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
-            View enterSplitButton, View settingsButton, View dismissButton) {
+            View settingsButton, View dismissButton) {
         mViewRoot = viewRoot;
         mTopEndContainer = topEndContainer;
         mDragHandle = dragHandle;
-        mEnterSplitButton = enterSplitButton;
         mSettingsButton = settingsButton;
         mDismissButton = dismissButton;
+
+        bindInitialViewState();
     }
 
     /**
@@ -70,4 +72,22 @@
             v.setLayoutParams(params);
         }
     }
+
+    /** Calculate the initial state of the menu icons. Called when the menu is first created. */
+    private void bindInitialViewState() {
+        if (mViewRoot == null || mTopEndContainer == null || mDragHandle == null
+                || mSettingsButton == null || mDismissButton == null) {
+            Log.e(TAG, "One of the required views is null.");
+            return;
+        }
+        // The menu view layout starts out with the settings button aligned at the top|end of the
+        // view group next to the dismiss button. On phones, the settings button should be aligned
+        // to the top|start of the view, so move it to parent view group to then align it to the
+        // top|start of the menu.
+        mTopEndContainer.removeView(mSettingsButton);
+        mViewRoot.addView(mSettingsButton);
+
+        setLayoutGravity(mDragHandle, Gravity.START | Gravity.TOP);
+        setLayoutGravity(mSettingsButton, Gravity.START | Gravity.TOP);
+    }
 }
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 7bbebe5..8ef2b6b 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
@@ -99,7 +99,7 @@
     private static final float MENU_BACKGROUND_ALPHA = 0.3f;
     private static final float DISABLED_ACTION_ALPHA = 0.54f;
 
-    private static final boolean ENABLE_ENTER_SPLIT = false;
+    private static final boolean ENABLE_RESIZE_HANDLE = false;
 
     private int mMenuState;
     private boolean mAllowMenuTimeout = true;
@@ -139,7 +139,7 @@
     protected View mViewRoot;
     protected View mSettingsButton;
     protected View mDismissButton;
-    protected View mEnterSplitButton;
+    protected View mResizeHandle;
     protected View mTopEndContainer;
     protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
 
@@ -177,23 +177,14 @@
             }
         });
 
-        mEnterSplitButton = findViewById(R.id.enter_split);
-        mEnterSplitButton.setAlpha(0);
-        mEnterSplitButton.setOnClickListener(v -> {
-            if (mMenuContainer.getAlpha() != 0) {
-                enterSplit();
-            }
-        });
-
-        findViewById(R.id.resize_handle).setAlpha(0);
-
+        mResizeHandle = findViewById(R.id.resize_handle);
+        mResizeHandle.setAlpha(0);
         mActionsGroup = findViewById(R.id.actions_group);
         mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
                 R.dimen.pip_between_action_padding_land);
         mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
         mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
-                findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton,
-                mDismissButton);
+                mResizeHandle, mSettingsButton, mDismissButton);
         mDismissFadeOutDurationMs = context.getResources()
                 .getInteger(R.integer.config_pipExitAnimationDuration);
 
@@ -277,13 +268,14 @@
                     mSettingsButton.getAlpha(), 1f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 1f);
-            ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
-                    mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f);
+            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
+                    mResizeHandle.getAlpha(),
+                    ENABLE_RESIZE_HANDLE && showResizeHandle ? 1f : 0f);
             if (menuState == MENU_STATE_FULL) {
                 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
-                        enterSplitAnim);
+                        resizeAnim);
             } else {
-                mMenuContainerAnimator.playTogether(enterSplitAnim);
+                mMenuContainerAnimator.playTogether(resizeAnim);
             }
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
             mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
@@ -336,7 +328,7 @@
         mMenuContainer.setAlpha(0f);
         mSettingsButton.setAlpha(0f);
         mDismissButton.setAlpha(0f);
-        mEnterSplitButton.setAlpha(0f);
+        mResizeHandle.setAlpha(0f);
     }
 
     void pokeMenu() {
@@ -376,10 +368,9 @@
                     mSettingsButton.getAlpha(), 0f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 0f);
-            ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
-                    mEnterSplitButton.getAlpha(), 0f);
-            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
-                    enterSplitAnim);
+            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
+                    mResizeHandle.getAlpha(), 0f);
+            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
             mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
             mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -531,14 +522,6 @@
         }
     }
 
-    private void enterSplit() {
-        // Do not notify menu visibility when hiding the menu, the controller will do this when it
-        // handles the message
-        hideMenu(mController::onEnterSplit, false /* notifyMenuVisibility */, true /* resize */,
-                ANIM_TYPE_HIDE);
-    }
-
-
     private void showSettings() {
         final Pair<ComponentName, Integer> topPipActivityInfo =
                 PipUtils.getTopPipActivity(mContext);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index c634b7f..dbd09fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -338,29 +338,22 @@
      * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
      *      * fullscreen depending on the display area's windowing mode.
      */
-    void expandLeavePip(boolean skipAnimation) {
-        expandLeavePip(skipAnimation, false /* enterSplit */);
-    }
-
-    /**
-     * Resizes the pinned task to split-screen mode.
-     */
-    void expandIntoSplit() {
-        expandLeavePip(false, true /* enterSplit */);
+    void expandLeavePip() {
+        expandLeavePip(false /* skipAnimation */);
     }
 
     /**
      * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
      * fullscreen depending on the display area's windowing mode.
      */
-    private void expandLeavePip(boolean skipAnimation, boolean enterSplit) {
+    void expandLeavePip(boolean skipAnimation) {
         if (DEBUG) {
             Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
         cancelPhysicsAnimation();
         mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
-        mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit);
+        mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 570fd5e..9f2f6a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -139,12 +139,7 @@
 
         @Override
         public void onPipExpand() {
-            mMotionHelper.expandLeavePip(false /* skipAnimation */);
-        }
-
-        @Override
-        public void onEnterSplit() {
-            mMotionHelper.expandIntoSplit();
+            mMotionHelper.expandLeavePip();
         }
 
         @Override
@@ -904,7 +899,7 @@
                     // Expand to fullscreen if this is a double tap
                     // the PiP should be frozen until the transition ends
                     setTouchEnabled(false);
-                    mMotionHelper.expandLeavePip(false /* skipAnimation */);
+                    mMotionHelper.expandLeavePip();
                 }
             } else if (mMenuState != MENU_STATE_FULL) {
                 if (mPipBoundsState.isStashed()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 00083d9..a2e9b64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -219,7 +219,7 @@
     public void movePipToFullscreen() {
         if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState));
 
-        mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
+        mPipTaskOrganizer.exitPip(mResizeAnimationDuration);
         onPipDisappeared();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7457be2..04058ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -202,25 +202,11 @@
         return moveToSideStage(task, sideStagePosition);
     }
 
-    public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition,
-            WindowContainerTransaction wct) {
-        final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
-        if (task == null) {
-            throw new IllegalArgumentException("Unknown taskId" + taskId);
-        }
-        return moveToSideStage(task, sideStagePosition, wct);
-    }
-
     public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
             @SplitPosition int sideStagePosition) {
         return mStageCoordinator.moveToSideStage(task, sideStagePosition);
     }
 
-    public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition, WindowContainerTransaction wct) {
-        return mStageCoordinator.moveToSideStage(task, sideStagePosition, wct);
-    }
-
     public boolean removeFromSideStage(int taskId) {
         return mStageCoordinator.removeFromSideStage(taskId);
     }
@@ -238,11 +224,6 @@
                 leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
     }
 
-    public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
-        moveToSideStage(taskId,
-                leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
-    }
-
     public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
         mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3c35e6a..8471e1e 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
@@ -280,11 +280,6 @@
     boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
             @SplitPosition int sideStagePosition) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        return moveToSideStage(task, sideStagePosition, wct);
-    }
-
-    boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition, WindowContainerTransaction wct) {
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         setSideStagePosition(sideStagePosition, wct);
         mSideStage.evictAllChildren(evictWct);
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index d80699d..f49e80a 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -1,3 +1,4 @@
 # Bug component: 909476
 # includes OWNERS from parent directories
 natanieljr@google.com
+pablogamito@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index 038be9c..ecc2d31 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -52,9 +52,9 @@
     testSpec: FlickerTestParameter
 ) : AppPairsTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
                 nonResizeableApp?.launchViaIntent(wmHelper)
                 // TODO pair apps through normal UX flow
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index bbc6b2d..04c82e5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -46,9 +46,9 @@
 class AppPairsTestPairPrimaryAndSecondaryApps(
     testSpec: FlickerTestParameter
 ) : AppPairsTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
                 // TODO pair apps through normal UX flow
                 executeShellCommand(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index bb784a8..b7d3ba6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -52,9 +52,9 @@
     testSpec: FlickerTestParameter
 ) : AppPairsTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
                 nonResizeableApp?.launchViaIntent(wmHelper)
                 // TODO pair apps through normal UX flow
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index a1a4db1..f6ce3d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -47,9 +47,9 @@
 class AppPairsTestUnpairPrimaryAndSecondaryApps(
     testSpec: FlickerTestParameter
 ) : AppPairsTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             setup {
                 eachRun {
                     executeShellCommand(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
index 9e20bbb..863c3af 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
@@ -25,14 +25,11 @@
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isRotated
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -50,7 +47,6 @@
 abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) {
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     protected val context: Context = instrumentation.context
-    protected val isRotated = testSpec.config.startRotation.isRotated()
     protected val activityHelper = ActivityHelper.getInstance()
     protected val appPairsHelper = AppPairsHelper(instrumentation,
         Components.SplitScreenActivity.LABEL,
@@ -82,20 +78,18 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            withTestName { testSpec.name }
-            repeat { testSpec.config.repetitions }
-            transition(this, testSpec.config)
+            transition(this)
         }
     }
 
-    internal open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
+    internal open val transition: FlickerBuilder.() -> Unit
+        get() = {
             setup {
                 test {
                     device.wakeUpAndGoToHomeScreen()
                 }
                 eachRun {
-                    this.setRotation(configuration.startRotation)
+                    this.setRotation(testSpec.startRotation)
                     primaryApp.launchViaIntent(wmHelper)
                     secondaryApp.launchViaIntent(wmHelper)
                     nonResizeableApp?.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index 56a2531..13824b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
@@ -50,14 +49,14 @@
 class RotateTwoLaunchedAppsInAppPairsMode(
     testSpec: FlickerTestParameter
 ) : RotateTwoLaunchedAppsTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
                 executeShellCommand(composePairsCommand(
                     primaryTaskId, secondaryTaskId, true /* pair */))
                 waitAppsShown(primaryApp, secondaryApp)
-                setRotation(testSpec.config.endRotation)
+                setRotation(testSpec.endRotation)
             }
         }
 
@@ -85,13 +84,13 @@
     @Presubmit
     @Test
     fun appPairsPrimaryBoundsIsVisibleAtEnd() =
-        testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+        testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
             primaryApp.component)
 
     @FlakyTest
     @Test
     fun appPairsSecondaryBoundsIsVisibleAtEnd() =
-        testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+        testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
             secondaryApp.component)
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
index 0699a4f..c003084 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
@@ -50,11 +49,11 @@
 class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
     testSpec: FlickerTestParameter
 ) : RotateTwoLaunchedAppsTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
-                this.setRotation(testSpec.config.endRotation)
+                this.setRotation(testSpec.endRotation)
                 executeShellCommand(
                     composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
                 waitAppsShown(primaryApp, secondaryApp)
@@ -93,13 +92,13 @@
     @FlakyTest(bugId = 172776659)
     @Test
     fun appPairsPrimaryBoundsIsVisibleAtEnd() =
-        testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+        testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
             primaryApp.component)
 
     @FlakyTest(bugId = 172776659)
     @Test
     fun appPairsSecondaryBoundsIsVisibleAtEnd() =
-        testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+        testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
             secondaryApp.component)
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
index b95193a1..670fbd8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
@@ -34,7 +34,7 @@
     override val nonResizeableApp: SplitScreenHelper?
         get() = null
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
                 test {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index b432bb6..99f7e23 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -32,7 +32,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.flicker.repetitions
 import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
 import org.junit.runners.Parameterized
 
@@ -51,14 +50,13 @@
     protected val uid = context.packageManager.getApplicationInfo(
             testApp.component.packageName, 0).uid
 
-    protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    protected abstract val transition: FlickerBuilder.() -> Unit
 
     @JvmOverloads
     protected open fun buildTransition(
-        extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {}
-    ): FlickerBuilder.(Map<String, Any?>) -> Unit {
-        return { configuration ->
-
+        extraSpec: FlickerBuilder.() -> Unit = {}
+    ): FlickerBuilder.() -> Unit {
+        return {
             setup {
                 test {
                     notifyManager.setBubblesAllowed(testApp.component.packageName,
@@ -75,7 +73,7 @@
                 testApp.exit()
             }
 
-            extraSpec(this, configuration)
+            extraSpec(this)
         }
     }
 
@@ -87,8 +85,7 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            repeat { testSpec.config.repetitions }
-            transition(this, testSpec.config)
+            transition(this)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index 80acd3d..1605d80 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -49,7 +49,7 @@
     private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
     private val displaySize = DisplayMetrics()
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index 66520d2..d415aae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -44,7 +44,7 @@
 @Group4
 class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
                 test {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index bbdf997..61e27f2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -42,7 +42,7 @@
 @Group4
 class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index 97c27c7..a8f17a75 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -41,7 +41,7 @@
 @Group4
 class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             transitions {
                 val addBubbleBtn = waitAndGetAddBubbleBtn()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index d92e047..8e5a33c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -43,7 +43,7 @@
 @Group4
 class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
                 test {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
index bd44d08..c86a122 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -28,7 +28,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
@@ -53,9 +52,9 @@
 class EnterSplitScreenDockActivity(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             transitions {
                 device.launchSplitScreen(wmHelper)
             }
@@ -69,7 +68,7 @@
     @Presubmit
     @Test
     fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             splitScreenApp.component)
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
index 625d48b..2f9244b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
@@ -48,9 +48,9 @@
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            cleanSetup(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            cleanSetup(this)
             setup {
                 eachRun {
                     splitScreenApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
index 2ed2806..1740c3e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
@@ -52,9 +51,9 @@
 class EnterSplitScreenLaunchToSide(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             transitions {
                 device.launchSplitScreen(wmHelper)
                 device.reopenAppFromOverview(wmHelper)
@@ -69,13 +68,13 @@
     @Presubmit
     @Test
     fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             splitScreenApp.component)
 
     @Presubmit
     @Test
     fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             secondaryApp.component)
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
index ee6cf34..4c063b9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
@@ -55,9 +55,9 @@
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            cleanSetup(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            cleanSetup(this)
             setup {
                 eachRun {
                     nonResizeableApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
index 163b6ffda..f75dee6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
@@ -54,9 +54,9 @@
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            cleanSetup(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            cleanSetup(this)
             setup {
                 eachRun {
                     nonResizeableApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
index 2b629b0..3885155 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -50,9 +50,9 @@
 class ExitLegacySplitScreenFromBottom(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             setup {
                 eachRun {
                     splitScreenApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
index 95fe3be..d913a6d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -51,9 +51,9 @@
 class ExitPrimarySplitScreenShowSecondaryFullscreen(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             teardown {
                 eachRun {
                     secondaryApp.exit(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
index f7d628d..f3ff7b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
@@ -53,9 +53,9 @@
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            cleanSetup(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            cleanSetup(this)
             setup {
                 eachRun {
                     splitScreenApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
index a5c6571..42e707a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
@@ -53,9 +53,9 @@
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            cleanSetup(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            cleanSetup(this)
             setup {
                 eachRun {
                     splitScreenApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
index 6f486b0..079a6ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
@@ -55,9 +55,9 @@
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            cleanSetup(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            cleanSetup(this)
             setup {
                 eachRun {
                     nonResizeableApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
index f03c927..6ac8683 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
@@ -54,9 +54,9 @@
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            cleanSetup(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            cleanSetup(this)
             setup {
                 eachRun {
                     nonResizeableApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
index 1e89a25..b01f41c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
@@ -26,7 +26,7 @@
 abstract class LegacySplitScreenRotateTransition(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 2ccd03b..5fe13e0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -25,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.exitSplitScreen
 import com.android.server.wm.flicker.helpers.launchSplitScreen
@@ -61,8 +60,8 @@
 ) : LegacySplitScreenTransition(testSpec) {
     private val testApp = SimpleAppHelper(instrumentation)
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
             setup {
                 test {
                     device.wakeUpAndGoToHomeScreen()
@@ -70,7 +69,7 @@
                 }
                 eachRun {
                     testApp.launchViaIntent(wmHelper)
-                    this.setRotation(configuration.endRotation)
+                    this.setRotation(testSpec.endRotation)
                     device.launchSplitScreen(wmHelper)
                     device.waitForIdle()
                 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
index 661c8b6..a4a1f61 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -25,12 +25,9 @@
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isRotated
 import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
@@ -45,7 +42,6 @@
 abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) {
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     protected val context: Context = instrumentation.context
-    protected val isRotated = testSpec.config.startRotation.isRotated()
     protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
     protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
     protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
@@ -82,15 +78,15 @@
         FlickerComponentName.SPLASH_SCREEN,
         FlickerComponentName.SNAPSHOT)
 
-    protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
+    protected open val transition: FlickerBuilder.() -> Unit
+        get() = {
             setup {
                 eachRun {
                     device.wakeUpAndGoToHomeScreen()
                     device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
                     secondaryApp.launchViaIntent(wmHelper)
                     splitScreenApp.launchViaIntent(wmHelper)
-                    this.setRotation(configuration.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             teardown {
@@ -105,19 +101,17 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            withTestName { testSpec.name }
-            repeat { testSpec.config.repetitions }
-            transition(this, testSpec.config)
+            transition(this)
         }
     }
 
-    internal open val cleanSetup: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
+    internal open val cleanSetup: FlickerBuilder.() -> Unit
+        get() = {
             setup {
                 eachRun {
                     device.wakeUpAndGoToHomeScreen()
                     device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
-                    this.setRotation(configuration.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             teardown {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
index 34eff80..087b21c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -49,9 +49,9 @@
 class OpenAppToLegacySplitScreen(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             transitions {
                 device.launchSplitScreen(wmHelper)
                 wmHelper.waitForAppTransitionIdle()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 58e1def..a238bc2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -37,7 +37,6 @@
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -69,12 +68,12 @@
     private val testAppTop = SimpleAppHelper(instrumentation)
     private val testAppBottom = ImeAppHelper(instrumentation)
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
             setup {
                 eachRun {
                     device.wakeUpAndGoToHomeScreen()
-                    this.setRotation(configuration.startRotation)
+                    this.setRotation(testSpec.startRotation)
                     this.launcherStrategy.clearRecentAppsFromOverview()
                     testAppBottom.launchViaIntent(wmHelper)
                     device.pressHome()
@@ -220,8 +219,8 @@
                 .map {
                     val description = (startRatio.toString().replace("/", "-") + "_to_" +
                         stopRatio.toString().replace("/", "-"))
-                    val newName = "${FlickerTestParameter.defaultName(it.config)}_$description"
-                    FlickerTestParameter(it.config, name = newName)
+                    val newName = "${FlickerTestParameter.defaultName(it)}_$description"
+                    FlickerTestParameter(it.config, nameOverride = newName)
                 }
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
index 8a50bc0..50cd548 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -29,7 +29,6 @@
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -53,12 +52,12 @@
 class RotateOneLaunchedAppAndEnterSplitScreen(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenRotateTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             transitions {
                 device.launchSplitScreen(wmHelper)
-                this.setRotation(testSpec.config.startRotation)
+                this.setRotation(testSpec.startRotation)
             }
         }
 
@@ -69,7 +68,7 @@
     @Presubmit
     @Test
     fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             splitScreenApp.component)
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
index 84676a9..8d52225 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -29,7 +29,6 @@
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -53,11 +52,11 @@
 class RotateOneLaunchedAppInSplitScreenMode(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenRotateTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             transitions {
-                this.setRotation(testSpec.config.startRotation)
+                this.setRotation(testSpec.startRotation)
                 device.launchSplitScreen(wmHelper)
             }
         }
@@ -69,7 +68,7 @@
     @Presubmit
     @Test
     fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(
-        testSpec.config.startRotation, splitScreenApp.component)
+        testSpec.startRotation, splitScreenApp.component)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
index 2abdca9..070f636 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -29,7 +29,6 @@
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -54,11 +53,11 @@
 class RotateTwoLaunchedAppAndEnterSplitScreen(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenRotateTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             transitions {
-                this.setRotation(testSpec.config.startRotation)
+                this.setRotation(testSpec.startRotation)
                 device.launchSplitScreen(wmHelper)
                 device.reopenAppFromOverview(wmHelper)
             }
@@ -71,13 +70,13 @@
     @Presubmit
     @Test
     fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             splitScreenApp.component)
 
     @Presubmit
     @Test
     fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             secondaryApp.component)
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
index fe9b9f5..fabbd26 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -30,7 +30,6 @@
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
@@ -55,18 +54,18 @@
 class RotateTwoLaunchedAppInSplitScreenMode(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenRotateTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            super.transition(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             setup {
                 eachRun {
                     device.launchSplitScreen(wmHelper)
                     device.reopenAppFromOverview(wmHelper)
-                    this.setRotation(testSpec.config.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             transitions {
-                this.setRotation(testSpec.config.startRotation)
+                this.setRotation(testSpec.startRotation)
             }
         }
 
@@ -77,13 +76,13 @@
     @Presubmit
     @Test
     fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             splitScreenApp.component)
 
     @Presubmit
     @Test
     fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
-        testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+        testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
             secondaryApp.component)
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 52a744f..33626d0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -58,7 +58,7 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = true, stringExtras = emptyMap()) {
             transitions {
                 pipApp.clickEnterPipButton(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index c8c3f4d..791505b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -74,9 +74,9 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            setupAndTeardown(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setupAndTeardown(this)
 
             setup {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 64b7eb5..8267442 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -102,7 +102,7 @@
     }
 
     /**
-     * Checks that the visible region of [pipApp] covers the full display area at the end of
+     * Checks that the visible region oft [pipApp] covers the full display area at the end of
      * the transition
      */
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 5207fed..6c9fed9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -23,18 +23,17 @@
 import com.android.server.wm.flicker.LAUNCHER_COMPONENT
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
 import org.junit.Test
 
 /**
  * Base class for exiting pip (closing pip window) without returning to the app
  */
 abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = buildTransition(eachRun = true) { configuration ->
+    override val transition: FlickerBuilder.() -> Unit
+        get() = buildTransition(eachRun = true) {
             setup {
                 eachRun {
-                    this.setRotation(configuration.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             teardown {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index b53342d..5f29dbc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -59,7 +59,7 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = true) {
             setup {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 1fec3cf..00ccf26 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -56,7 +56,7 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = true) {
             setup {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 73626c2..b0b11e9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -52,9 +52,9 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group3
 class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
                 pipApp.closePipWindow(wmHelper)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 9e43dee..f4eb701 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -55,9 +55,9 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group3
 class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { args ->
-            super.transition(this, args)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             transitions {
                 val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds
                 val pipCenterX = pipRegion.centerX()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index d0fee9a..f196764 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -55,7 +55,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group3
 class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = true) {
             transitions {
                 pipApp.doubleClickPipWindow(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 0ab857d..d9685f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -59,7 +59,7 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = false) {
             teardown {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index e507edf..c6b42ea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -59,7 +59,7 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = false) {
             teardown {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index aba8ace..45cbdc8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -26,7 +26,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.wm.shell.flicker.helpers.ImeAppHelper
 import org.junit.FixMethodOrder
@@ -47,12 +46,12 @@
 class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     private val imeApp = ImeAppHelper(instrumentation)
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = buildTransition(eachRun = false) { configuration ->
+    override val transition: FlickerBuilder.() -> Unit
+        get() = buildTransition(eachRun = false) {
             setup {
                 test {
                     imeApp.launchViaIntent(wmHelper)
-                    setRotation(configuration.startRotation)
+                    setRotation(testSpec.startRotation)
                 }
             }
             teardown {
@@ -78,7 +77,7 @@
     @Test
     fun pipInVisibleBounds() {
         testSpec.assertWm {
-            val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+            val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
             coversAtMost(displayBounds, pipApp.component)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index 9bea5c0..3e3ea16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
@@ -64,10 +63,8 @@
         assumeFalse(isShellTransitionsEnabled())
     }
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            withTestName { testSpec.name }
-            repeat { testSpec.config.repetitions }
             setup {
                 test {
                     removeAllTasksButHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 669f37a..af984b3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -25,12 +25,10 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.FixMethodOrder
@@ -47,7 +45,7 @@
  * Actions:
  *     Launch a [pipApp] in pip mode
  *     Launch another app [fixedApp] (appears below pip)
- *     Rotate the screen from [testSpec.config.startRotation] to [testSpec.config.endRotation]
+ *     Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
  *     (usually, 0->90 and 90->0)
  *
  * Notes:
@@ -65,21 +63,21 @@
 @Group4
 class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     private val fixedApp = FixedAppHelper(instrumentation)
-    private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
-    private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.config.endRotation)
+    private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
+    private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = buildTransition(eachRun = false) { configuration ->
+    override val transition: FlickerBuilder.() -> Unit
+        get() = buildTransition(eachRun = false) {
             setup {
                 test {
                     fixedApp.launchViaIntent(wmHelper)
                 }
                 eachRun {
-                    setRotation(configuration.startRotation)
+                    setRotation(testSpec.startRotation)
                 }
             }
             transitions {
-                setRotation(configuration.endRotation)
+                setRotation(testSpec.endRotation)
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index e8a61e8..93a4e1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -26,15 +26,12 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isRotated
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -44,11 +41,10 @@
 
 abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    protected val isRotated = testSpec.config.startRotation.isRotated()
     protected val pipApp = PipAppHelper(instrumentation)
-    protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+    protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
     protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
-    protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    protected abstract val transition: FlickerBuilder.() -> Unit
     // Helper class to process test actions by broadcast.
     protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
         private fun createIntentWithAction(broadcastAction: String): Intent {
@@ -81,16 +77,14 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            withTestName { testSpec.name }
-            repeat { testSpec.config.repetitions }
-            transition(this, testSpec.config)
+            transition(this)
         }
     }
 
     /**
      * Gets a configuration that handles basic setup and teardown of pip tests
      */
-    protected val setupAndTeardown: FlickerBuilder.(Map<String, Any?>) -> Unit
+    protected val setupAndTeardown: FlickerBuilder.() -> Unit
         get() = {
             setup {
                 test {
@@ -121,10 +115,10 @@
     protected open fun buildTransition(
         eachRun: Boolean,
         stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
-        extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {}
-    ): FlickerBuilder.(Map<String, Any?>) -> Unit {
-        return { configuration ->
-            setupAndTeardown(this, configuration)
+        extraSpec: FlickerBuilder.() -> Unit = {}
+    ): FlickerBuilder.() -> Unit {
+        return {
+            setupAndTeardown(this)
 
             setup {
                 test {
@@ -155,7 +149,7 @@
                 }
             }
 
-            extraSpec(this, configuration)
+            extraSpec(this)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index d6dbc36..f8e2d38 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -50,9 +50,9 @@
     private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
     private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { configuration ->
-            setupAndTeardown(this, configuration)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setupAndTeardown(this)
 
             setup {
                 eachRun {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 0172cf32..0270093 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -50,7 +50,6 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,8 +75,7 @@
     @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
-    @Mock private Optional<LegacySplitScreenController> mMockOptionalLegacySplitScreen;
-    @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
+    @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
@@ -101,9 +99,8 @@
                 mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
                 mPipBoundsAlgorithm, mMockPhonePipMenuController,
                 mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
-                mMockPipTransitionController, mMockOptionalLegacySplitScreen,
-                mMockOptionalSplitScreen, mMockDisplayController, mMockPipUiEventLogger,
-                mMockShellTaskOrganizer, mMainExecutor));
+                mMockPipTransitionController, mMockOptionalSplitScreen, mMockDisplayController,
+                mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
         mMainExecutor.flushAll();
         preparePipTaskOrg();
     }
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 610fd80..17f5164 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,6 +1,6 @@
 set noparent
 toddke@google.com
-rtmitchell@google.com
+zyy@google.com
 patb@google.com
 
 per-file CursorWindow.cpp=omakoto@google.com
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index cae2d0b..5e8a623 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2677,30 +2677,27 @@
                 // DENSITY_ANY is now dealt with. We should look to
                 // pick a density bucket and potentially scale it.
                 // Any density is potentially useful
-                // because the system will scale it.  Scaling down
-                // is generally better than scaling up.
+                // because the system will scale it.  Always prefer
+                // scaling down.
                 int h = thisDensity;
                 int l = otherDensity;
                 bool bImBigger = true;
                 if (l > h) {
-                    int t = h;
-                    h = l;
-                    l = t;
+                    std::swap(l, h);
                     bImBigger = false;
                 }
 
-                if (requestedDensity >= h) {
-                    // requested value higher than both l and h, give h
+                if (h == requestedDensity) {
+                    // This handles the case where l == h == requestedDensity.
+                    // In that case, this and o are equally good so both
+                    // true and false are valid. This preserves previous
+                    // behavior.
                     return bImBigger;
-                }
-                if (l >= requestedDensity) {
+                } else if (l >= requestedDensity) {
                     // requested value lower than both l and h, give l
                     return !bImBigger;
-                }
-                // saying that scaling down is 2x better than up
-                if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) {
-                    return !bImBigger;
                 } else {
+                    // otherwise give h
                     return bImBigger;
                 }
             }
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index b54915f..698c36f 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -75,6 +75,9 @@
   configs.add(buildDensityConfig(int(ResTable_config::DENSITY_HIGH) + 20));
   ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
 
+  configs.add(buildDensityConfig(int(ResTable_config::DENSITY_XHIGH) - 1));
+  ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
+
   expectedBest = buildDensityConfig(ResTable_config::DENSITY_XHIGH);
   configs.add(expectedBest);
   ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs));
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 85baa3d..4daedfc 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -60,10 +60,10 @@
  with {@link MediaExtractor}, {@link MediaSync}, {@link MediaMuxer}, {@link MediaCrypto},
  {@link MediaDrm}, {@link Image}, {@link Surface}, and {@link AudioTrack}.)
  <p>
- <center><object style="width: 540px; height: 205px;" type="image/svg+xml"
-   data="../../../images/media/mediacodec_buffers.svg"><img
-   src="../../../images/media/mediacodec_buffers.png" style="width: 540px; height: 205px"
-   alt="MediaCodec buffer flow diagram"></object></center>
+ <center>
+   <img src="../../../images/media/mediacodec_buffers.svg" style="width: 540px; height: 205px"
+       alt="MediaCodec buffer flow diagram">
+ </center>
  <p>
  In broad terms, a codec processes input data to generate output data. It processes data
  asynchronously and uses a set of input and output buffers. At a simplistic level, you request
@@ -268,10 +268,10 @@
  Uninitialized, Configured and Error, whereas the Executing state conceptually progresses through
  three sub-states: Flushed, Running and End-of-Stream.
  <p>
- <center><object style="width: 516px; height: 353px;" type="image/svg+xml"
-   data="../../../images/media/mediacodec_states.svg"><img
-   src="../../../images/media/mediacodec_states.png" style="width: 519px; height: 356px"
-   alt="MediaCodec state diagram"></object></center>
+ <center>
+   <img src="../../../images/media/mediacodec_states.svg" style="width: 519px; height: 356px"
+       alt="MediaCodec state diagram">
+ </center>
  <p>
  When you create a codec using one of the factory methods, the codec is in the Uninitialized
  state. First, you need to configure it via {@link #configure configure(&hellip;)}, which brings
@@ -513,10 +513,10 @@
  Similarly, upon an initial call to {@code start} the codec will move directly to the Running
  sub-state and start passing available input buffers via the callback.
  <p>
- <center><object style="width: 516px; height: 353px;" type="image/svg+xml"
-   data="../../../images/media/mediacodec_async_states.svg"><img
-   src="../../../images/media/mediacodec_async_states.png" style="width: 516px; height: 353px"
-   alt="MediaCodec state diagram for asynchronous operation"></object></center>
+ <center>
+   <img src="../../../images/media/mediacodec_async_states.svg" style="width: 516px; height: 353px"
+       alt="MediaCodec state diagram for asynchronous operation">
+ </center>
  <p>
  MediaCodec is typically used like this in asynchronous mode:
  <pre class=prettyprint>
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 052b465..3027838 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -278,8 +278,12 @@
 Result DvrClient::close() {
     if (mDvrMQEventFlag != nullptr) {
         EventFlag::deleteEventFlag(&mDvrMQEventFlag);
+        mDvrMQEventFlag = nullptr;
     }
-    mDvrMQ = nullptr;
+    if (mDvrMQ != nullptr) {
+        delete mDvrMQ;
+        mDvrMQ = nullptr;
+    }
 
     if (mTunerDvr != nullptr) {
         Status s = mTunerDvr->close();
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index fe746fa..e8b3de8 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -177,11 +177,14 @@
 }
 
 Result FilterClient::close() {
-    if (mFilterMQEventFlag) {
+    if (mFilterMQEventFlag != nullptr) {
         EventFlag::deleteEventFlag(&mFilterMQEventFlag);
+        mFilterMQEventFlag = nullptr;
     }
-    mFilterMQEventFlag = nullptr;
-    mFilterMQ = nullptr;
+    if (mFilterMQ != nullptr) {
+        delete mFilterMQ;
+        mFilterMQ = nullptr;
+    }
 
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->close();
diff --git a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
index 794b0eb..280e407 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
@@ -15,13 +15,8 @@
  */
 package com.android.settingslib;
 
-import com.android.settingslib.mobile.TelephonyIcons;
-
-import java.text.SimpleDateFormat;
-import java.util.Objects;
-
 /**
- * Icons and states for SysUI and Settings.
+ * Icons for SysUI and Settings.
  */
 public class SignalIcon {
 
@@ -71,92 +66,6 @@
     }
 
     /**
-     * Holds states for SysUI.
-     */
-    public static class State {
-        // No locale as it's only used for logging purposes
-        private static SimpleDateFormat sSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-        public boolean connected;
-        public boolean enabled;
-        public boolean activityIn;
-        public boolean activityOut;
-        public int level;
-        public IconGroup iconGroup;
-        public int inetCondition;
-        public int rssi; // Only for logging.
-
-        // Not used for comparison, just used for logging.
-        public long time;
-
-        /**
-         * Generates a copy of the source state.
-         */
-        public void copyFrom(State state) {
-            connected = state.connected;
-            enabled = state.enabled;
-            level = state.level;
-            iconGroup = state.iconGroup;
-            inetCondition = state.inetCondition;
-            activityIn = state.activityIn;
-            activityOut = state.activityOut;
-            rssi = state.rssi;
-            time = state.time;
-        }
-
-        @Override
-        public String toString() {
-            if (time != 0) {
-                StringBuilder builder = new StringBuilder();
-                toString(builder);
-                return builder.toString();
-            } else {
-                return "Empty " + getClass().getSimpleName();
-            }
-        }
-
-        protected void toString(StringBuilder builder) {
-            builder.append("connected=").append(connected).append(',')
-                .append("enabled=").append(enabled).append(',')
-                .append("level=").append(level).append(',')
-                .append("inetCondition=").append(inetCondition).append(',')
-                .append("iconGroup=").append(iconGroup).append(',')
-                .append("activityIn=").append(activityIn).append(',')
-                .append("activityOut=").append(activityOut).append(',')
-                .append("rssi=").append(rssi).append(',')
-                .append("lastModified=").append(sSDF.format(time));
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!o.getClass().equals(getClass())) {
-                return false;
-            }
-            State other = (State) o;
-            return other.connected == connected
-                    && other.enabled == enabled
-                    && other.level == level
-                    && other.inetCondition == inetCondition
-                    && other.iconGroup == iconGroup
-                    && other.activityIn == activityIn
-                    && other.activityOut == activityOut
-                    && other.rssi == rssi;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(
-                    connected,
-                    enabled,
-                    level,
-                    inetCondition,
-                    iconGroup,
-                    activityIn,
-                    activityOut,
-                    rssi);
-        }
-    }
-
-    /**
      * Holds icons for a given MobileState.
      */
     public static class MobileIconGroup extends IconGroup {
@@ -189,110 +98,4 @@
             this.dataType = dataType;
         }
     }
-
-    /**
-     * Holds mobile states for SysUI.
-     */
-    public static class MobileState extends State {
-        public String networkName;
-        public String networkNameData;
-        public boolean dataSim;
-        public boolean dataConnected;
-        public boolean isEmergency;
-        public boolean airplaneMode;
-        public boolean carrierNetworkChangeMode;
-        public boolean isDefault;
-        public boolean userSetup;
-        public boolean roaming;
-        public boolean defaultDataOff;  // Tracks the on/off state of the defaultDataSubscription
-
-        @Override
-        public void copyFrom(State s) {
-            super.copyFrom(s);
-            MobileState state = (MobileState) s;
-            dataSim = state.dataSim;
-            networkName = state.networkName;
-            networkNameData = state.networkNameData;
-            dataConnected = state.dataConnected;
-            isDefault = state.isDefault;
-            isEmergency = state.isEmergency;
-            airplaneMode = state.airplaneMode;
-            carrierNetworkChangeMode = state.carrierNetworkChangeMode;
-            userSetup = state.userSetup;
-            roaming = state.roaming;
-            defaultDataOff = state.defaultDataOff;
-        }
-
-        /** @return true if this state is disabled or not default data */
-        public boolean isDataDisabledOrNotDefault() {
-            return (iconGroup == TelephonyIcons.DATA_DISABLED
-                    || (iconGroup == TelephonyIcons.NOT_DEFAULT_DATA)) && userSetup;
-        }
-
-        /** @return if this state is considered to have inbound activity */
-        public boolean hasActivityIn() {
-            return dataConnected && !carrierNetworkChangeMode && activityIn;
-        }
-
-        /** @return if this state is considered to have outbound activity */
-        public boolean hasActivityOut() {
-            return dataConnected && !carrierNetworkChangeMode && activityOut;
-        }
-
-        /** @return true if this state should show a RAT icon in quick settings */
-        public boolean showQuickSettingsRatIcon() {
-            return dataConnected || isDataDisabledOrNotDefault();
-        }
-
-        @Override
-        protected void toString(StringBuilder builder) {
-            super.toString(builder);
-            builder.append(',');
-            builder.append("dataSim=").append(dataSim).append(',');
-            builder.append("networkName=").append(networkName).append(',');
-            builder.append("networkNameData=").append(networkNameData).append(',');
-            builder.append("dataConnected=").append(dataConnected).append(',');
-            builder.append("roaming=").append(roaming).append(',');
-            builder.append("isDefault=").append(isDefault).append(',');
-            builder.append("isEmergency=").append(isEmergency).append(',');
-            builder.append("airplaneMode=").append(airplaneMode).append(',');
-            builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode)
-                    .append(',');
-            builder.append("userSetup=").append(userSetup).append(',');
-            builder.append("defaultDataOff=").append(defaultDataOff).append(',');
-            builder.append("showQuickSettingsRatIcon=").append(showQuickSettingsRatIcon());
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            return super.equals(o)
-                    && Objects.equals(((MobileState) o).networkName, networkName)
-                    && Objects.equals(((MobileState) o).networkNameData, networkNameData)
-                    && ((MobileState) o).dataSim == dataSim
-                    && ((MobileState) o).dataConnected == dataConnected
-                    && ((MobileState) o).isEmergency == isEmergency
-                    && ((MobileState) o).airplaneMode == airplaneMode
-                    && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
-                    && ((MobileState) o).userSetup == userSetup
-                    && ((MobileState) o).isDefault == isDefault
-                    && ((MobileState) o).roaming == roaming
-                    && ((MobileState) o).defaultDataOff == defaultDataOff;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(super.hashCode(),
-                    networkName,
-                    networkNameData,
-                    dataSim,
-                    dataConnected,
-                    isEmergency,
-                    airplaneMode,
-                    carrierNetworkChangeMode,
-                    userSetup,
-                    isDefault,
-                    roaming,
-                    defaultDataOff);
-        }
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 6cb60d1..7390b6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -97,6 +97,9 @@
 
     /**
      * Logs a simple action without page id or attribution
+     *
+     * @param category the target page
+     * @param taggedData the data for {@link EventLogWriter}
      */
     public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
         for (LogWriter writer : mLoggerWriters) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 93f900d..2bd5bdc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -257,14 +257,6 @@
         VALIDATORS.put(Global.Wearable.SYSTEM_CAPABILITIES, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.SYSTEM_EDITION, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_PLATFORM_MR_NUMBER, ANY_INTEGER_VALIDATOR);
-        VALIDATORS.put(Global.Wearable.BOTTOM_OFFSET, ANY_INTEGER_VALIDATOR);
-        VALIDATORS.put(
-                Global.Wearable.DISPLAY_SHAPE,
-                new DiscreteValueValidator(
-                        new String[] {
-                            String.valueOf(Global.Wearable.DISPLAY_SHAPE_ROUND),
-                            String.valueOf(Global.Wearable.DISPLAY_SHAPE_SQUARE)
-                        }));
         VALIDATORS.put(Global.Wearable.MOBILE_SIGNAL_DETECTOR, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.AMBIENT_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.AMBIENT_TILT_TO_WAKE, BOOLEAN_VALIDATOR);
@@ -299,7 +291,6 @@
                             String.valueOf(Global.Wearable.HFP_CLIENT_ENABLED),
                             String.valueOf(Global.Wearable.HFP_CLIENT_DISABLED)
                         }));
-        VALIDATORS.put(Global.Wearable.HFP_CLIENT_PROFILE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.COMPANION_OS_VERSION, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.ENABLE_ALL_LANGUAGES, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.OEM_SETUP_VERSION, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ea46ef1..cd4047b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -5294,11 +5294,6 @@
                             Global.Wearable.WEAR_PLATFORM_MR_NUMBER,
                             SystemProperties.getInt("ro.cw_build.platform_mr", 0));
                     initGlobalSettingsDefaultValForWearLocked(
-                            Settings.Global.Wearable.BOTTOM_OFFSET, 0);
-                    initGlobalSettingsDefaultValForWearLocked(
-                            Settings.Global.Wearable.DISPLAY_SHAPE,
-                            Settings.Global.Wearable.DISPLAY_SHAPE_SQUARE);
-                    initGlobalSettingsDefaultValForWearLocked(
                             Settings.Global.Wearable.SCREEN_BRIGHTNESS_LEVEL,
                             getContext()
                                     .getResources()
@@ -5345,8 +5340,6 @@
                             Settings.Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN,
                             SystemProperties.getInt("ro.ambient.plugged_timeout_min", -1));
                     initGlobalSettingsDefaultValForWearLocked(
-                            Settings.Global.Wearable.COMPANION_ADDRESS, "");
-                    initGlobalSettingsDefaultValForWearLocked(
                             Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE,
                             Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN);
                     initGlobalSettingsDefaultValForWearLocked(
@@ -5359,12 +5352,6 @@
                             disabledProfileSetting.isNull()
                                     ? 0
                                     : Long.parseLong(disabledProfileSetting.getValue());
-                    final boolean isHfpClientProfileEnabled =
-                            (disabledProfileSettingValue & (1 << BluetoothProfile.HEADSET_CLIENT))
-                                    == 0;
-                    initGlobalSettingsDefaultValForWearLocked(
-                            Settings.Global.Wearable.HFP_CLIENT_PROFILE_ENABLED,
-                            isHfpClientProfileEnabled);
                     initGlobalSettingsDefaultValForWearLocked(
                             Settings.Global.Wearable.COMPANION_OS_VERSION,
                             Settings.Global.Wearable.COMPANION_OS_VERSION_UNDEFINED);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 82012d9..c05e01d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -632,9 +632,6 @@
                     Settings.Global.Wearable.SYSTEM_CAPABILITIES,
                     Settings.Global.Wearable.SYSTEM_EDITION,
                     Settings.Global.Wearable.WEAR_PLATFORM_MR_NUMBER,
-                    Settings.Global.Wearable.COMPANION_BT_ADDRESS_DUAL,
-                    Settings.Global.Wearable.DISPLAY_SHAPE,
-                    Settings.Global.Wearable.BOTTOM_OFFSET,
                     Settings.Global.Wearable.SCREEN_BRIGHTNESS_LEVEL,
                     Settings.Global.Wearable.MOBILE_SIGNAL_DETECTOR,
                     Settings.Global.Wearable.AMBIENT_ENABLED,
@@ -647,12 +644,10 @@
                     Settings.Global.Wearable.AMBIENT_GESTURE_SENSOR_ID,
                     Settings.Global.Wearable.AMBIENT_LOW_BIT_ENABLED,
                     Settings.Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN,
-                    Settings.Global.Wearable.COMPANION_ADDRESS,
                     Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE,
                     Settings.Global.Wearable.COMPANION_BLE_ROLE,
                     Settings.Global.Wearable.COMPANION_NAME,
                     Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
-                    Settings.Global.Wearable.HFP_CLIENT_PROFILE_ENABLED,
                     Settings.Global.Wearable.COMPANION_OS_VERSION,
                     Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
                     Settings.Global.Wearable.SETUP_LOCALE,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b620654..f59f099 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -470,8 +470,7 @@
     <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
     <uses-permission android:name="android.permission.SUGGEST_EXTERNAL_TIME" />
 
-    <!-- Permissions needed for testing locale manager service -->
-    <!-- todo(b/201957547): Add CTS test name when available-->
+    <!-- Permissions needed for CTS test - CtsLocaleManagerTestCases -->
     <uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
 
     <!-- Permission required for CTS test - android.server.biometrics -->
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 6b91d64..2f117fc 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -50,17 +50,6 @@
     srcs: ["src/com/android/systemui/EventLogTags.logtags"],
 }
 
-java_library {
-    name: "SystemUI-flags",
-    srcs: [
-        "src/com/android/systemui/flags/Flags.java",
-    ],
-    libs: [
-        "SystemUI-flag-types",
-    ],
-    static_kotlin_stdlib: false,
-}
-
 filegroup {
     name: "ReleaseJavaFiles",
     srcs: [
@@ -127,7 +116,6 @@
         "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
-        "SystemUI-flags",
         "monet",
         "dagger2",
         "jsr330",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 26a4962..e1e9420 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -366,10 +366,6 @@
                   android:exported="false"
                   android:finishOnTaskLaunch="true" />
 
-        <activity android:name=".screenrecord.ScreenRecordDialog"
-            android:theme="@style/ScreenRecord"
-            android:showForAllUsers="true"
-            android:excludeFromRecents="true" />
         <service android:name=".screenrecord.RecordingService" />
 
         <receiver android:name=".SysuiRestartReceiver"
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 1cf14f2..ce23a8b 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -22,6 +22,7 @@
 hyunyoungs@google.com
 jaggies@google.com
 jamesoleary@google.com
+jbolinger@google.com
 jdemeulenaere@google.com
 jeffdq@google.com
 jjaggi@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 865f96b..faa7554 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -16,11 +16,16 @@
 
 package com.android.systemui.animation
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
 import android.app.Dialog
 import android.content.Context
 import android.graphics.Color
+import android.graphics.Rect
 import android.os.Looper
 import android.util.Log
+import android.util.MathUtils
 import android.view.GhostView
 import android.view.Gravity
 import android.view.View
@@ -32,6 +37,7 @@
 import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
 import android.view.WindowManagerPolicyConstants
 import android.widget.FrameLayout
+import kotlin.math.roundToInt
 
 private const val TAG = "DialogLaunchAnimator"
 
@@ -52,7 +58,8 @@
     private val currentAnimations = hashSetOf<DialogLaunchAnimation>()
 
     /**
-     * Show [dialog] by expanding it from [view].
+     * Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then
+     * the background of the dialog will be animated when the dialog bounds change.
      *
      * Caveats: When calling this function, the dialog content view will actually be stolen and
      * attached to a different dialog (and thus a different window) which means that the actual
@@ -60,7 +67,12 @@
      * must call dismiss(), hide() and show() on the [Dialog] returned by this function to actually
      * dismiss, hide or show the dialog.
      */
-    fun showFromView(dialog: Dialog, view: View): Dialog {
+    @JvmOverloads
+    fun showFromView(
+        dialog: Dialog,
+        view: View,
+        animateBackgroundBoundsChange: Boolean = false
+    ): Dialog {
         if (Looper.myLooper() != Looper.getMainLooper()) {
             throw IllegalStateException(
                 "showFromView must be called from the main thread and dialog must be created in " +
@@ -78,7 +90,8 @@
 
         val launchAnimation = DialogLaunchAnimation(
             context, launchAnimator, hostDialogProvider, view,
-            onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog)
+            onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog,
+            animateBackgroundBoundsChange)
         val hostDialog = launchAnimation.hostDialog
         currentAnimations.add(launchAnimation)
 
@@ -208,7 +221,10 @@
     private val onDialogDismissed: (DialogLaunchAnimation) -> Unit,
 
     /** The original dialog whose content will be shown and animate in/out in [hostDialog]. */
-    private val originalDialog: Dialog
+    private val originalDialog: Dialog,
+
+    /** Whether we should animate the dialog background when its bounds change. */
+    private val animateBackgroundBoundsChange: Boolean
 ) {
     /**
      * The fullscreen dialog to which we will add the content view [originalDialogView] of
@@ -221,10 +237,11 @@
     private val hostDialogRoot = FrameLayout(context)
 
     /**
-     * The content view of [originalDialog], which will be stolen from that dialog and added to
-     * [hostDialogRoot].
+     * The parent of the original dialog content view, that serves as a fake window that will have
+     * the same size as the original dialog window and to which we will set the original dialog
+     * window background.
      */
-    private var originalDialogView: View? = null
+    private val dialogContentParent = FrameLayout(context)
 
     /**
      * The background color of [originalDialogView], taking into consideration the [originalDialog]
@@ -246,6 +263,11 @@
 
     private var isTouchSurfaceGhostDrawn = false
     private var isOriginalDialogViewLaidOut = false
+    private var backgroundLayoutListener = if (animateBackgroundBoundsChange) {
+        AnimatedBoundsLayoutListener()
+    } else {
+        null
+    }
 
     fun start() {
         // Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
@@ -374,9 +396,6 @@
     }
 
     private fun showDialogFromView(dialogView: View) {
-        // Save the dialog view for later as we will need it for the close animation.
-        this.originalDialogView = dialogView
-
         // Close the dialog when clicking outside of it.
         hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
         dialogView.isClickable = true
@@ -394,17 +413,13 @@
             throw IllegalStateException("Dialogs with no backgrounds on window are not supported")
         }
 
-        dialogView.setBackgroundResource(backgroundRes)
-        originalDialogBackgroundColor =
-            GhostedViewLaunchAnimatorController.findGradientDrawable(dialogView.background!!)
-                ?.color
-                ?.defaultColor ?: Color.BLACK
-
-        // Add the dialog view to the host (fullscreen) dialog and make it invisible to make sure
-        // it's not drawn yet.
-        (dialogView.parent as? ViewGroup)?.removeView(dialogView)
+        // Add a parent view to the original dialog view to which we will set the original dialog
+        // window background. This View serves as a fake window with background, so that we are sure
+        // that we don't override the dialog view paddings with the window background that usually
+        // has insets.
+        dialogContentParent.setBackgroundResource(backgroundRes)
         hostDialogRoot.addView(
-            dialogView,
+            dialogContentParent,
 
             // We give it the size of its original dialog window.
             FrameLayout.LayoutParams(
@@ -413,10 +428,31 @@
                 Gravity.CENTER
             )
         )
-        dialogView.visibility = View.INVISIBLE
+
+        // Make the dialog view parent invisible for now, to make sure it's not drawn yet.
+        dialogContentParent.visibility = View.INVISIBLE
+
+        val background = dialogContentParent.background!!
+        originalDialogBackgroundColor =
+            GhostedViewLaunchAnimatorController.findGradientDrawable(background)
+                ?.color
+                ?.defaultColor ?: Color.BLACK
+
+        // Add the dialog view to its parent (that has the original window background).
+        (dialogView.parent as? ViewGroup)?.removeView(dialogView)
+        dialogContentParent.addView(
+            dialogView,
+
+            // It should match its parent size, which is sized the same as the original dialog
+            // window.
+            FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+            )
+        )
 
         // Start the animation when the dialog is laid out in the center of the host dialog.
-        dialogView.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
+        dialogContentParent.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
             override fun onLayoutChange(
                 view: View,
                 left: Int,
@@ -428,7 +464,7 @@
                 oldRight: Int,
                 oldBottom: Int
             ) {
-                dialogView.removeOnLayoutChangeListener(this)
+                dialogContentParent.removeOnLayoutChangeListener(this)
 
                 isOriginalDialogViewLaidOut = true
                 maybeStartLaunchAnimation()
@@ -479,6 +515,13 @@
                 if (dismissRequested) {
                     hostDialog.dismiss()
                 }
+
+                // If necessary, we animate the dialog background when its bounds change. We do it
+                // at the end of the launch animation, because the lauch animation already correctly
+                // handles bounds changes.
+                if (backgroundLayoutListener != null) {
+                    dialogContentParent.addOnLayoutChangeListener(backgroundLayoutListener)
+                }
             }
         )
     }
@@ -548,7 +591,11 @@
                 }
 
                 touchSurface.visibility = View.VISIBLE
-                originalDialogView!!.visibility = View.INVISIBLE
+                dialogContentParent.visibility = View.INVISIBLE
+
+                if (backgroundLayoutListener != null) {
+                    dialogContentParent.removeOnLayoutChangeListener(backgroundLayoutListener)
+                }
 
                 // The animated ghost was just removed. We create a temporary ghost that will be
                 // removed only once we draw the touch surface, to avoid flickering that would
@@ -578,12 +625,10 @@
         onLaunchAnimationStart: () -> Unit = {},
         onLaunchAnimationEnd: () -> Unit = {}
     ) {
-        val dialogView = this.originalDialogView!!
-
         // Create 2 ghost controllers to animate both the dialog and the touch surface in the host
         // dialog.
-        val startView = if (isLaunching) touchSurface else dialogView
-        val endView = if (isLaunching) dialogView else touchSurface
+        val startView = if (isLaunching) touchSurface else dialogContentParent
+        val endView = if (isLaunching) dialogContentParent else touchSurface
         val startViewController = GhostedViewLaunchAnimatorController(startView)
         val endViewController = GhostedViewLaunchAnimatorController(endView)
         startViewController.launchContainer = hostDialogRoot
@@ -662,4 +707,81 @@
 
         return (touchSurface.parent as? View)?.isShown ?: true
     }
+
+    /** A layout listener to animate the change of bounds of the dialog background.  */
+    class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener {
+        companion object {
+            private const val ANIMATION_DURATION = 500L
+        }
+
+        private var lastBounds: Rect? = null
+        private var currentAnimator: ValueAnimator? = null
+
+        override fun onLayoutChange(
+            view: View,
+            left: Int,
+            top: Int,
+            right: Int,
+            bottom: Int,
+            oldLeft: Int,
+            oldTop: Int,
+            oldRight: Int,
+            oldBottom: Int
+        ) {
+            // Don't animate if bounds didn't actually change.
+            if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) {
+                // Make sure that we that the last bounds set by the animator were not overridden.
+                lastBounds?.let { bounds ->
+                    view.left = bounds.left
+                    view.top = bounds.top
+                    view.right = bounds.right
+                    view.bottom = bounds.bottom
+                }
+                return
+            }
+
+            if (lastBounds == null) {
+                lastBounds = Rect(oldLeft, oldTop, oldRight, oldBottom)
+            }
+
+            val bounds = lastBounds!!
+            val startLeft = bounds.left
+            val startTop = bounds.top
+            val startRight = bounds.right
+            val startBottom = bounds.bottom
+
+            currentAnimator?.cancel()
+            currentAnimator = null
+
+            val animator = ValueAnimator.ofFloat(0f, 1f).apply {
+                duration = ANIMATION_DURATION
+                interpolator = Interpolators.STANDARD
+
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        currentAnimator = null
+                    }
+                })
+
+                addUpdateListener { animatedValue ->
+                    val progress = animatedValue.animatedFraction
+
+                    // Compute new bounds.
+                    bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt()
+                    bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt()
+                    bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt()
+                    bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt()
+
+                    // Set the new bounds.
+                    view.left = bounds.left
+                    view.top = bounds.top
+                    view.right = bounds.right
+                    view.bottom = bounds.bottom
+                }
+            }
+
+            currentAnimator = animator
+            animator.start()
+        }
+    }
 }
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
index c415ecd..88914ded 100644
--- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
@@ -20,5 +20,5 @@
     android:viewportHeight="24">
     <path
         android:fillColor="@android:color/white"
-        android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" />
+        android:pathData="@*android:string/config_work_badge_path_24" />
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 543b7d7..9495ee6 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -16,78 +16,74 @@
   ~ limitations under the License.
   -->
 
-<FrameLayout
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="24dp"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
+>
+    <TextView
+        android:id="@+id/title"
         android:layout_height="wrap_content"
-        android:padding="24dp"
-        android:layout_marginStart="16dp"
-        android:layout_marginEnd="16dp"
-    >
-        <TextView
-            android:id="@+id/title"
-            android:layout_height="wrap_content"
-            android:layout_width="0dp"
-            android:textAlignment="center"
-            android:text="@string/qs_user_switch_dialog_title"
-            android:textAppearance="@style/TextAppearance.QSDialog.Title"
-            android:layout_marginBottom="32dp"
-            sysui:layout_constraintTop_toTopOf="parent"
-            sysui:layout_constraintStart_toStartOf="parent"
-            sysui:layout_constraintEnd_toEndOf="parent"
-            sysui:layout_constraintBottom_toTopOf="@id/grid"
-            />
-
-        <com.android.systemui.qs.PseudoGridView
-            android:id="@+id/grid"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="28dp"
-            sysui:verticalSpacing="4dp"
-            sysui:horizontalSpacing="4dp"
-            sysui:fixedChildWidth="80dp"
-            sysui:layout_constraintTop_toBottomOf="@id/title"
-            sysui:layout_constraintStart_toStartOf="parent"
-            sysui:layout_constraintEnd_toEndOf="parent"
-            sysui:layout_constraintBottom_toTopOf="@id/barrier"
+        android:layout_width="0dp"
+        android:textAlignment="center"
+        android:text="@string/qs_user_switch_dialog_title"
+        android:textAppearance="@style/TextAppearance.QSDialog.Title"
+        android:layout_marginBottom="32dp"
+        sysui:layout_constraintTop_toTopOf="parent"
+        sysui:layout_constraintStart_toStartOf="parent"
+        sysui:layout_constraintEnd_toEndOf="parent"
+        sysui:layout_constraintBottom_toTopOf="@id/grid"
         />
 
-        <androidx.constraintlayout.widget.Barrier
-            android:id="@+id/barrier"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            sysui:barrierDirection="top"
-            sysui:constraint_referenced_ids="settings,done"
-            />
+    <com.android.systemui.qs.PseudoGridView
+        android:id="@+id/grid"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="28dp"
+        sysui:verticalSpacing="4dp"
+        sysui:horizontalSpacing="4dp"
+        sysui:fixedChildWidth="80dp"
+        sysui:layout_constraintTop_toBottomOf="@id/title"
+        sysui:layout_constraintStart_toStartOf="parent"
+        sysui:layout_constraintEnd_toEndOf="parent"
+        sysui:layout_constraintBottom_toTopOf="@id/barrier"
+    />
 
-        <Button
-            android:id="@+id/settings"
-            android:layout_width="wrap_content"
-            android:layout_height="48dp"
-            android:text="@string/quick_settings_more_user_settings"
-            sysui:layout_constraintTop_toBottomOf="@id/barrier"
-            sysui:layout_constraintBottom_toBottomOf="parent"
-            sysui:layout_constraintStart_toStartOf="parent"
-            sysui:layout_constraintEnd_toStartOf="@id/done"
-            sysui:layout_constraintHorizontal_chainStyle="spread_inside"
-            style="@style/Widget.QSDialog.Button.BorderButton"
-            />
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/barrier"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        sysui:barrierDirection="top"
+        sysui:constraint_referenced_ids="settings,done"
+        />
 
-        <Button
-            android:id="@+id/done"
-            android:layout_width="wrap_content"
-            android:layout_height="48dp"
-            android:text="@string/quick_settings_done"
-            sysui:layout_constraintTop_toBottomOf="@id/barrier"
-            sysui:layout_constraintBottom_toBottomOf="parent"
-            sysui:layout_constraintStart_toEndOf="@id/settings"
-            sysui:layout_constraintEnd_toEndOf="parent"
-            style="@style/Widget.QSDialog.Button"
-            />
+    <Button
+        android:id="@+id/settings"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:text="@string/quick_settings_more_user_settings"
+        sysui:layout_constraintTop_toBottomOf="@id/barrier"
+        sysui:layout_constraintBottom_toBottomOf="parent"
+        sysui:layout_constraintStart_toStartOf="parent"
+        sysui:layout_constraintEnd_toStartOf="@id/done"
+        sysui:layout_constraintHorizontal_chainStyle="spread_inside"
+        style="@style/Widget.QSDialog.Button.BorderButton"
+        />
 
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</FrameLayout>
\ No newline at end of file
+    <Button
+        android:id="@+id/done"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:text="@string/quick_settings_done"
+        sysui:layout_constraintTop_toBottomOf="@id/barrier"
+        sysui:layout_constraintBottom_toBottomOf="parent"
+        sysui:layout_constraintStart_toEndOf="@id/settings"
+        sysui:layout_constraintEnd_toEndOf="parent"
+        style="@style/Widget.QSDialog.Button"
+        />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index c122829..e43a149 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -17,8 +17,7 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:background="@drawable/rounded_bg_full">
+    android:orientation="vertical">
 
     <!-- Scrollview is necessary to fit everything in landscape layout -->
     <ScrollView
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fbbbf9f..974eda7 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -883,6 +883,7 @@
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:stateListAnimator">@null</item>
+        <item name="android:layout_marginHorizontal">4dp</item>
     </style>
 
     <style name="Widget.QSDialog.Button.BorderButton">
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 2909043..d172006 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -45,9 +45,6 @@
         ":wm_shell-aidls",
         ":wm_shell_util-sources",
     ],
-    libs: [
-        "SystemUI-flags",
-    ],
     static_libs: [
         "PluginCoreLib",
         "androidx.dynamicanimation_dynamicanimation",
@@ -75,7 +72,6 @@
     ],
     static_kotlin_stdlib: false,
     libs: [
-        "SystemUI-flags",
         "androidx.concurrent_concurrent-futures",
     ],
     static_libs: [
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index d9b6a34..9574101 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -22,15 +22,21 @@
 interface Flag<T> : Parcelable {
     val id: Int
     val default: T
+    val resourceOverride: Int
 
     override fun describeContents() = 0
+
+    fun hasResourceOverride(): Boolean {
+        return resourceOverride != -1
+    }
 }
 
 // Consider using the "parcelize" kotlin library.
 
 data class BooleanFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Boolean = false
+    override val default: Boolean = false,
+    override val resourceOverride: Int = -1
 ) : Flag<Boolean> {
 
     companion object {
@@ -54,7 +60,8 @@
 
 data class StringFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: String = ""
+    override val default: String = "",
+    override val resourceOverride: Int = -1
 ) : Flag<String> {
     companion object {
         @JvmField
@@ -77,7 +84,8 @@
 
 data class IntFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Int = 0
+    override val default: Int = 0,
+    override val resourceOverride: Int = -1
 ) : Flag<Int> {
 
     companion object {
@@ -101,7 +109,8 @@
 
 data class LongFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Long = 0
+    override val default: Long = 0,
+    override val resourceOverride: Int = -1
 ) : Flag<Long> {
 
     companion object {
@@ -125,7 +134,8 @@
 
 data class FloatFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Float = 0f
+    override val default: Float = 0f,
+    override val resourceOverride: Int = -1
 ) : Flag<Float> {
 
     companion object {
@@ -149,7 +159,8 @@
 
 data class DoubleFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Double = 0.0
+    override val default: Double = 0.0,
+    override val resourceOverride: Int = -1
 ) : Flag<Double> {
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index cfef6cb..907943a 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -261,8 +261,10 @@
             mCarrierTextCallback = callback;
             if (mNetworkSupported.get()) {
                 // Keyguard update monitor expects callbacks from main thread
-                mMainExecutor.execute(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
-                mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+                mMainExecutor.execute(() -> {
+                    mKeyguardUpdateMonitor.registerCallback(mCallback);
+                    mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+                });
                 mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
             } else {
                 // Don't listen and clear out the text when the device isn't a phone.
@@ -272,8 +274,10 @@
             }
         } else {
             mCarrierTextCallback = null;
-            mMainExecutor.execute(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
-            mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+            mMainExecutor.execute(() -> {
+                mKeyguardUpdateMonitor.removeCallback(mCallback);
+                mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+            });
             mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a41a497..85bc8f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -102,7 +102,6 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -323,7 +322,6 @@
     private boolean mLockIconPressed;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private final Executor mBackgroundExecutor;
-    private int mLockScreenMode;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -1736,11 +1734,11 @@
             DumpManager dumpManager,
             RingerModeTracker ringerModeTracker,
             @Background Executor backgroundExecutor,
+            @Main Executor mainExecutor,
             StatusBarStateController statusBarStateController,
             LockPatternUtils lockPatternUtils,
             AuthController authController,
             TelephonyListenerManager telephonyListenerManager,
-            FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
             LatencyTracker latencyTracker) {
         mContext = context;
@@ -1966,6 +1964,17 @@
             mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
         }
 
+        // in case authenticators aren't registered yet at this point:
+        mAuthController.addCallback(new AuthController.Callback() {
+            @Override
+            public void onAllAuthenticatorsRegistered() {
+            }
+
+            @Override
+            public void onEnrollmentsChanged() {
+                mainExecutor.execute(() -> updateBiometricListeningState());
+            }
+        });
         updateBiometricListeningState();
         if (mFpm != null) {
             mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
@@ -2043,7 +2052,7 @@
 
     /**
      * @return if udfps is available on this device. will return true even if the user hasn't
-     * enrolled udfps.
+     * enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered.
      */
     public boolean isUdfpsAvailable() {
         return mAuthController.getUdfpsProps() != null
@@ -2092,7 +2101,6 @@
             return;
         }
 
-        // TODO: Add support for multiple fingerprint sensors, b/173730729
         updateUdfpsEnrolled(getCurrentUser());
         final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsEnrolled());
         final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 3c80a18..8a0b5b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -695,11 +695,20 @@
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
         public void onAllAuthenticatorsRegistered() {
-            // must be called from the main thread since it may update the views
-            mExecutor.execute(() -> {
-                updateIsUdfpsEnrolled();
-                updateConfiguration();
-            });
+            updateUdfpsConfig();
+        }
+
+        @Override
+        public void onEnrollmentsChanged() {
+            updateUdfpsConfig();
         }
     };
+
+    private void updateUdfpsConfig() {
+        // must be called from the main thread since it may update the views
+        mExecutor.execute(() -> {
+            updateIsUdfpsEnrolled();
+            updateConfiguration();
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index 59d9aff..d2703f5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -22,6 +22,7 @@
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static java.util.Objects.requireNonNull;
 
@@ -659,6 +660,7 @@
                         | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                 PixelFormat.TRANSLUCENT);
         params.receiveInsetsIgnoringZOrder = true;
+        params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
         params.windowAnimations = android.R.style.Animation_Translucent;
         params.gravity = Gravity.START | Gravity.TOP;
         params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 5472d6b..7215736 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -142,6 +142,10 @@
                     mUdfpsEnrolledForUser.put(userId, hasEnrollments);
                 }
             }
+
+            for (Callback cb : mCallbacks) {
+                cb.onEnrollmentsChanged();
+            }
         }
     };
 
@@ -844,5 +848,11 @@
          * registered before this call, this callback will never be triggered.
          */
         void onAllAuthenticatorsRegistered();
+
+        /**
+         * Called when UDFPS enrollments have changed. This is called after boot and on changes to
+         * enrollment.
+         */
+        void onEnrollmentsChanged();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 8b04bf5..ec17d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -292,10 +292,16 @@
             }
         }
 
-    private val authControllerCallback = AuthController.Callback {
-        updateSensorLocation()
-        updateUdfpsDependentParams()
-    }
+    private val authControllerCallback =
+        object : AuthController.Callback {
+            override fun onAllAuthenticatorsRegistered() {
+                updateSensorLocation()
+                updateUdfpsDependentParams()
+            }
+
+            override fun onEnrollmentsChanged() {
+            }
+        }
 
     private fun updateUdfpsDependentParams() {
         authController.udfpsProps?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index de8ed70..bce8784 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -22,7 +22,6 @@
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
-import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.screenshot.LongScreenshotActivity;
 import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
 import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
@@ -67,12 +66,6 @@
     @ClassKey(BrightnessDialog.class)
     public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
 
-    /** Inject into ScreenRecordDialog */
-    @Binds
-    @IntoMap
-    @ClassKey(ScreenRecordDialog.class)
-    public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
-
     /** Inject into UsbDebuggingActivity. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 11a0f6c..3c86760 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -60,6 +60,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
@@ -206,7 +207,9 @@
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarStateController statusBarStateController, ShadeController shadeController,
             ConfigurationController configurationController,
-            @Nullable IStatusBarService statusBarService, INotificationManager notificationManager,
+            @Nullable IStatusBarService statusBarService,
+            INotificationManager notificationManager,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManagerLegacy groupManager, NotificationEntryManager entryManager,
@@ -215,6 +218,7 @@
         return Optional.ofNullable(BubblesManager.create(context, bubblesOptional,
                 notificationShadeWindowController, statusBarStateController, shadeController,
                 configurationController, statusBarService, notificationManager,
+                visibilityProvider,
                 interruptionStateProvider, zenModeController, notifUserManager,
                 groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager,
                 sysuiMainExecutor));
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 8f1486b..908397b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -107,6 +107,11 @@
                 public void onAllAuthenticatorsRegistered() {
                     updateUdfpsController();
                 }
+
+                @Override
+                public void onEnrollmentsChanged() {
+                    updateUdfpsController();
+                }
             });
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
deleted file mode 100644
index c87a7e4..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags;
-
-import android.content.res.Resources;
-import android.util.SparseArray;
-
-import androidx.annotation.BoolRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.wrapper.BuildInfo;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-/**
- * Reads and caches feature flags for quick access
- *
- * Feature flags must be defined as boolean resources. For example:
- *
- * {@code
- *  <bool name="flag_foo_bar_baz">false</bool>
- * }
- *
- * It is strongly recommended that the name of the resource begin with "flag_".
- *
- * Flags can be overridden via adb on development builds. For example, to override the flag from the
- * previous example, do the following:
- *
- * {@code
- *  $ adb shell setprop persist.systemui.flag_foo_bar_baz 1
- * }
- *
- * Note that all storage keys begin with "flag_", even if their associated resId does not.
- *
- * Calls to this class should probably be wrapped by a method in {@link FeatureFlags}.
- */
-@SysUISingleton
-public class FeatureFlagReader implements Dumpable {
-    private final Resources mResources;
-    private final boolean mAreFlagsOverrideable;
-    private final SystemPropertiesHelper mSystemPropertiesHelper;
-    private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>();
-
-    private FlagReader mFlagReader;
-
-    @Inject
-    public FeatureFlagReader(
-            @Main Resources resources,
-            BuildInfo build,
-            DumpManager dumpManager,
-            SystemPropertiesHelper systemPropertiesHelper,
-            FlagReader reader) {
-        mResources = resources;
-        mFlagReader = reader;
-        mSystemPropertiesHelper = systemPropertiesHelper;
-        mAreFlagsOverrideable =
-                build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable);
-        dumpManager.registerDumpable("FeatureFlags", this);
-    }
-
-    boolean isEnabled(BooleanFlag flag) {
-        return mFlagReader.isEnabled(flag.getId(), flag.getDefault());
-    }
-
-    void addListener(FlagReader.Listener listener) {
-        mFlagReader.addListener(listener);
-    }
-
-    void removeListener(FlagReader.Listener listener) {
-        mFlagReader.removeListener(listener);
-    }
-
-    /**
-     * Returns true if the specified feature flag has been enabled.
-     *
-     * @param resId The backing boolean resource that determines the value of the flag. This value
-     *              can be overridden via DeviceConfig on development builds.
-     */
-    public boolean isEnabled(@BoolRes int resId) {
-        synchronized (mCachedFlags) {
-            CachedFlag cachedFlag = mCachedFlags.get(resId);
-
-            if (cachedFlag == null) {
-                String name = resourceIdToFlagName(resId);
-                boolean value = mResources.getBoolean(resId);
-                if (mAreFlagsOverrideable) {
-                    value = mSystemPropertiesHelper.getBoolean(flagNameToStorageKey(name), value);
-                }
-
-                cachedFlag = new CachedFlag(name, value);
-                mCachedFlags.put(resId, cachedFlag);
-            }
-
-            return cachedFlag.value;
-        }
-    }
-
-    private String resourceIdToFlagName(@BoolRes int resId) {
-        String resName = mResources.getResourceEntryName(resId);
-        if (resName.startsWith(RESNAME_PREFIX)) {
-            resName = resName.substring(RESNAME_PREFIX.length());
-        }
-        return resName;
-    }
-
-    private String flagNameToStorageKey(String flagName) {
-        if (flagName.startsWith(STORAGE_KEY_PREFIX)) {
-            return flagName;
-        } else {
-            return STORAGE_KEY_PREFIX + flagName;
-        }
-    }
-
-    @Nullable
-    private String storageKeyToFlagName(String configName) {
-        if (configName.startsWith(STORAGE_KEY_PREFIX)) {
-            return configName.substring(STORAGE_KEY_PREFIX.length());
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        ArrayList<String> flagStrings = new ArrayList<>(mCachedFlags.size());
-        for (int i = 0; i < mCachedFlags.size(); i++) {
-            int key = mCachedFlags.keyAt(i);
-            // get the object by the key.
-            CachedFlag flag = mCachedFlags.get(key);
-            flagStrings.add("  " + RESNAME_PREFIX + flag.name + ": " + flag.value + "\n");
-        }
-        flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
-        pw.println("AreFlagsOverrideable: " + mAreFlagsOverrideable);
-        pw.println("Cached FeatureFlags:");
-        for (String flagString : flagStrings) {
-            pw.print(flagString);
-        }
-    }
-
-    private static class CachedFlag {
-        public final String name;
-        public final boolean value;
-
-        private CachedFlag(String name, boolean value) {
-            this.name = name;
-            this.value = value;
-        }
-    }
-
-    private static final String STORAGE_KEY_PREFIX = "persist.systemui.flag_";
-    private static final String RESNAME_PREFIX = "flag_";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 77e907c..6880674 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -17,13 +17,17 @@
 package com.android.systemui.flags;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
+import android.util.SparseArray;
 import android.widget.Toast;
 
+import androidx.annotation.BoolRes;
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -39,13 +43,16 @@
  */
 @SysUISingleton
 public class FeatureFlags {
-    private final FeatureFlagReader mFlagReader;
+    private final Resources mResources;
+    private final FlagReader mFlagReader;
     private final Context mContext;
     private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>();
     private final Map<Integer, List<Listener>> mListeners = new HashMap<>();
+    private final SparseArray<Boolean> mCachedFlags = new SparseArray<>();
 
     @Inject
-    public FeatureFlags(FeatureFlagReader flagReader, Context context) {
+    public FeatureFlags(@Main Resources resources, FlagReader flagReader, Context context) {
+        mResources = resources;
         mFlagReader = flagReader;
         mContext = context;
 
@@ -59,7 +66,7 @@
     };
 
     @VisibleForTesting
-    void addFlag(Flag flag) {
+    void addFlag(Flag<?> flag) {
         mFlagMap.put(flag.getId(), flag);
     }
 
@@ -68,7 +75,15 @@
      * @return The value of the flag.
      */
     public boolean isEnabled(BooleanFlag flag) {
-        return mFlagReader.isEnabled(flag);
+        boolean def = flag.getDefault();
+        if (flag.hasResourceOverride()) {
+            try {
+                def = isEnabledInOverlay(flag.getResourceOverride());
+            } catch (Resources.NotFoundException e) {
+                // no-op
+            }
+        }
+        return mFlagReader.isEnabled(flag.getId(), def);
     }
 
     /**
@@ -118,13 +133,11 @@
     }
 
     public boolean isPeopleTileEnabled() {
-        // TODO(b/202860494): different resource overlays have different values.
-        return mFlagReader.isEnabled(R.bool.flag_conversations);
+        return isEnabled(Flags.PEOPLE_TILE);
     }
 
     public boolean isMonetEnabled() {
-        // TODO(b/202860494): used in wallpaper picker. Always true, maybe delete.
-        return mFlagReader.isEnabled(R.bool.flag_monet);
+        return isEnabled(Flags.MONET);
     }
 
     public boolean isPMLiteEnabled() {
@@ -132,8 +145,7 @@
     }
 
     public boolean isChargingRippleEnabled() {
-        // TODO(b/202860494): different resource overlays have different values.
-        return mFlagReader.isEnabled(R.bool.flag_charging_ripple);
+        return isEnabled(Flags.CHARGING_RIPPLE);
     }
 
     public boolean isOngoingCallStatusBarChipEnabled() {
@@ -150,8 +162,7 @@
     }
 
     public boolean isSmartspaceEnabled() {
-        // TODO(b/202860494): different resource overlays have different values.
-        return mFlagReader.isEnabled(R.bool.flag_smartspace);
+        return isEnabled(Flags.SMARTSPACE);
     }
 
     public boolean isSmartspaceDedupingEnabled() {
@@ -163,7 +174,7 @@
     }
 
     public boolean isKeyguardQsUserDetailsShortcutEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_lockscreen_qs_user_detail_shortcut);
+        return isEnabled(Flags.QS_USER_DETAIL_SHORTCUT);
     }
 
     public boolean isSmartSpaceSharedElementTransitionEnabled() {
@@ -199,6 +210,16 @@
         return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
     }
 
+    private boolean isEnabledInOverlay(@BoolRes int resId) {
+        synchronized (mCachedFlags) {
+            if (!mCachedFlags.contains(resId)) {
+                mCachedFlags.put(resId, mResources.getBoolean(resId));
+            }
+
+            return mCachedFlags.get(resId);
+        }
+    }
+
     /** Simple interface for beinga alerted when a specific flag changes value. */
     public interface Listener {
         /** */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index f09c797..5be1cdf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.flags;
 
+import com.android.systemui.R;
+
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
@@ -47,7 +49,6 @@
     public static final BooleanFlag NOTIFICATION_UPDATES =
             new BooleanFlag(102, true);
 
-
     /***************************************/
     // 200 - keyguard/lockscreen
     public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -59,6 +60,9 @@
     public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
             new BooleanFlag(202, true);
 
+    public static final BooleanFlag CHARGING_RIPPLE =
+            new BooleanFlag(203, false, R.bool.flag_charging_ripple);
+
     /***************************************/
     // 300 - power menu
     public static final BooleanFlag POWER_MENU_LITE =
@@ -72,6 +76,9 @@
     public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
             new BooleanFlag(401, false);
 
+    public static final BooleanFlag SMARTSPACE =
+            new BooleanFlag(402, false, R.bool.flag_smartspace);
+
     /***************************************/
     // 500 - quick settings
     public static final BooleanFlag NEW_USER_SWITCHER =
@@ -80,6 +87,12 @@
     public static final BooleanFlag COMBINED_QS_HEADERS =
             new BooleanFlag(501, false);
 
+    public static final BooleanFlag PEOPLE_TILE =
+            new BooleanFlag(502, false, R.bool.flag_conversations);
+
+    public static final BooleanFlag QS_USER_DETAIL_SHORTCUT =
+            new BooleanFlag(503, false, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+
     /***************************************/
     // 600- status bar
     public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
@@ -96,6 +109,11 @@
     public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
             new BooleanFlag(702, true);
 
+    /***************************************/
+    // 800 - general visual/theme
+    public static final BooleanFlag MONET =
+            new BooleanFlag(800, true, R.bool.flag_monet);
+
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
     // |                                                           |
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index 79318d6..e1b97a4 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -36,8 +36,9 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.people.PeopleSpaceUtils;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.wm.shell.bubbles.Bubble;
 
@@ -50,7 +51,8 @@
     private static final String TAG = "PeopleSpaceLaunchConv";
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
     private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
-    private NotificationEntryManager mNotificationEntryManager;
+    private NotificationVisibilityProvider mVisibilityProvider;
+    private CommonNotifCollection mCommonNotifCollection;
     private final Optional<BubblesManager> mBubblesManagerOptional;
     private final UserManager mUserManager;
     private boolean mIsForTesting;
@@ -60,11 +62,16 @@
     private NotificationEntry mEntryToBubble;
 
     @Inject
-    public LaunchConversationActivity(NotificationEntryManager notificationEntryManager,
-            Optional<BubblesManager> bubblesManagerOptional, UserManager userManager,
-            CommandQueue commandQueue) {
+    public LaunchConversationActivity(
+            NotificationVisibilityProvider visibilityProvider,
+            CommonNotifCollection commonNotifCollection,
+            Optional<BubblesManager> bubblesManagerOptional,
+            UserManager userManager,
+            CommandQueue commandQueue
+    ) {
         super();
-        mNotificationEntryManager = notificationEntryManager;
+        mVisibilityProvider = visibilityProvider;
+        mCommonNotifCollection = commonNotifCollection;
         mBubblesManagerOptional = bubblesManagerOptional;
         mUserManager = userManager;
         mCommandQueue = commandQueue;
@@ -128,8 +135,7 @@
                 // shortcutId, fallback to notificationKey if it exists.
                 if (mBubblesManagerOptional.isPresent()) {
                     mBubble = mBubblesManagerOptional.get().getBubbleWithShortcutId(tileId);
-                    NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                            notificationKey);
+                    NotificationEntry entry = mCommonNotifCollection.getEntry(notificationKey);
                     if (mBubble != null || (entry != null && entry.canBubble())) {
                         mEntryToBubble = entry;
                         if (DEBUG) {
@@ -167,14 +173,14 @@
         }
 
         try {
-            if (mIStatusBarService == null || mNotificationEntryManager == null) {
+            if (mIStatusBarService == null || mCommonNotifCollection == null) {
                 if (DEBUG) {
                     Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
                 }
                 return;
             }
 
-            NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(notifKey);
+            NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
             if (entry == null || entry.getRanking() == null) {
                 if (DEBUG) {
                     Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
@@ -183,10 +189,8 @@
                 return;
             }
 
-            int count = mNotificationEntryManager.getActiveNotificationsCount();
-            int rank = entry.getRanking().getRank();
-            NotificationVisibility notifVisibility = NotificationVisibility.obtain(notifKey,
-                    rank, count, true);
+            NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
+            int rank = notifVisibility.rank;
 
             if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
             mIStatusBarService.onNotificationClear(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index fec61d9..141c246 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -19,6 +19,7 @@
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
 
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
@@ -42,8 +43,10 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.connectivity.IconState;
+import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.util.CarrierConfigTracker;
 
 import java.util.function.Consumer;
@@ -81,10 +84,9 @@
 
     private final SlotIndexResolver mSlotIndexResolver;
 
-    private final NetworkController.SignalCallback mSignalCallback =
-            new NetworkController.SignalCallback() {
+    private final SignalCallback mSignalCallback = new SignalCallback() {
                 @Override
-                public void setMobileDataIndicators(MobileDataIndicators indicators) {
+                public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
                     if (mProviderModel) {
                         return;
                     }
@@ -109,7 +111,7 @@
                 }
 
                 @Override
-                public void setCallIndicator(NetworkController.IconState statusIcon, int subId) {
+                public void setCallIndicator(@NonNull IconState statusIcon, int subId) {
                     if (!mProviderModel) {
                         return;
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 9de6ceb..0427e38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -18,6 +18,7 @@
 
 import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
 
+import android.annotation.NonNull;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
@@ -52,7 +53,8 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
@@ -273,10 +275,9 @@
         return mWifiConnected || mHotspotConnected;
     }
 
-    private final NetworkController.SignalCallback mSignalCallback =
-            new NetworkController.SignalCallback() {
+    private final SignalCallback mSignalCallback = new SignalCallback() {
                 @Override
-                public void setWifiIndicators(WifiIndicators indicators) {
+                public void setWifiIndicators(@NonNull WifiIndicators indicators) {
                     // statusIcon.visible has the connected status information
                     boolean enabledAndConnected = indicators.enabled
                             && (indicators.qsIcon == null ? false : indicators.qsIcon.visible);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 35dadd4..e5601f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
 
+import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.content.Context;
@@ -56,10 +57,10 @@
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.connectivity.IconState;
+import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import javax.inject.Inject;
@@ -269,7 +270,7 @@
         private final CallbackInfo mInfo = new CallbackInfo();
 
         @Override
-        public void setMobileDataIndicators(MobileDataIndicators indicators) {
+        public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
             if (indicators.qsIcon == null) {
                 // Not data sim, don't display.
                 return;
@@ -291,7 +292,7 @@
         }
 
         @Override
-        public void setIsAirplaneMode(IconState icon) {
+        public void setIsAirplaneMode(@NonNull IconState icon) {
             mInfo.airplaneModeEnabled = icon.visible;
             refreshState(mInfo);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 5a11ff8..7ba9cc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.qs.tiles;
 
-import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
@@ -29,6 +28,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -47,6 +47,7 @@
         DataSaverController.Listener{
 
     private final DataSaverController mDataSaverController;
+    private final DialogLaunchAnimator mDialogLaunchAnimator;
 
     @Inject
     public DataSaverTile(
@@ -58,11 +59,13 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            DataSaverController dataSaverController
+            DataSaverController dataSaverController,
+            DialogLaunchAnimator dialogLaunchAnimator
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDataSaverController = dataSaverController;
+        mDialogLaunchAnimator = dialogLaunchAnimator;
         mDataSaverController.observe(getLifecycle(), this);
     }
 
@@ -83,18 +86,27 @@
             toggleDataSaver();
             return;
         }
-        // Shows dialog first
-        SystemUIDialog dialog = new SystemUIDialog(mContext);
-        dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
-        dialog.setMessage(com.android.internal.R.string.data_saver_description);
-        dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
-                (OnClickListener) (dialogInterface, which) -> {
-                    toggleDataSaver();
-                    Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
-                });
-        dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
-        dialog.setShowForAllUsers(true);
-        dialog.show();
+
+        // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator must be created
+        // and shown on the main thread, so we post it to the UI handler.
+        mUiHandler.post(() -> {
+            SystemUIDialog dialog = new SystemUIDialog(mContext);
+            dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
+            dialog.setMessage(com.android.internal.R.string.data_saver_description);
+            dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
+                    (dialogInterface, which) -> {
+                        toggleDataSaver();
+                        Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
+                    });
+            dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
+            dialog.setShowForAllUsers(true);
+
+            if (view != null) {
+                mDialogLaunchAnimator.showFromView(dialog, view);
+            } else {
+                dialog.show();
+            }
+        });
     }
 
     private void toggleDataSaver() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 23b2a76..cd81b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -52,13 +53,13 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.statusbar.connectivity.AccessPointController;
+import com.android.systemui.statusbar.connectivity.IconState;
+import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIcons;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -250,7 +251,7 @@
 
 
         @Override
-        public void setWifiIndicators(WifiIndicators indicators) {
+        public void setWifiIndicators(@NonNull WifiIndicators indicators) {
             if (DEBUG) {
                 Log.d(TAG, "setWifiIndicators: " + indicators);
             }
@@ -271,7 +272,7 @@
         }
 
         @Override
-        public void setMobileDataIndicators(MobileDataIndicators indicators) {
+        public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
             if (DEBUG) {
                 Log.d(TAG, "setMobileDataIndicators: " + indicators);
             }
@@ -293,7 +294,7 @@
         }
 
         @Override
-        public void setEthernetIndicators(IconState icon) {
+        public void setEthernetIndicators(@NonNull IconState icon) {
             if (DEBUG) {
                 Log.d(TAG, "setEthernetIndicators: "
                         + "icon = " + (icon == null ? "" :  icon.toString()));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 24b9208..8ff75cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -39,7 +40,9 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
@@ -49,10 +52,13 @@
 public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
         implements RecordingController.RecordingStateChangeCallback {
     private static final String TAG = "ScreenRecordTile";
-    private RecordingController mController;
-    private KeyguardDismissUtil mKeyguardDismissUtil;
+    private final RecordingController mController;
+    private final KeyguardDismissUtil mKeyguardDismissUtil;
+    private final KeyguardStateController mKeyguardStateController;
+    private final Callback mCallback = new Callback();
+    private final DialogLaunchAnimator mDialogLaunchAnimator;
+
     private long mMillisUntilFinished = 0;
-    private Callback mCallback = new Callback();
 
     @Inject
     public ScreenRecordTile(
@@ -65,13 +71,17 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             RecordingController controller,
-            KeyguardDismissUtil keyguardDismissUtil
+            KeyguardDismissUtil keyguardDismissUtil,
+            KeyguardStateController keyguardStateController,
+            DialogLaunchAnimator dialogLaunchAnimator
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = controller;
         mController.observe(this, mCallback);
         mKeyguardDismissUtil = keyguardDismissUtil;
+        mKeyguardStateController = keyguardStateController;
+        mDialogLaunchAnimator = dialogLaunchAnimator;
     }
 
     @Override
@@ -89,7 +99,7 @@
         } else if (mController.isRecording()) {
             stopRecording();
         } else {
-            mUiHandler.post(() -> showPrompt());
+            mUiHandler.post(() -> showPrompt(view));
         }
         refreshState();
     }
@@ -136,15 +146,33 @@
         return mContext.getString(R.string.quick_settings_screen_record_label);
     }
 
-    private void showPrompt() {
-        // Close QS, otherwise the dialog appears beneath it
-        getHost().collapsePanels();
-        Intent intent = mController.getPromptIntent();
+    private void showPrompt(@Nullable View view) {
+        // We animate from the touched view only if we are not on the keyguard, given that if we
+        // are we will dismiss it which will also collapse the shade.
+        boolean shouldAnimateFromView = view != null && !mKeyguardStateController.isShowing();
+
+        // Create the recording dialog that will collapse the shade only if we start the recording.
+        Runnable onStartRecordingClicked = () -> {
+            // We dismiss the shade. Since starting the recording will also dismiss the dialog, we
+            // disable the exit animation which looks weird when it happens at the same time as the
+            // shade collapsing.
+            mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
+            getHost().collapsePanels();
+        };
+        ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext,
+                onStartRecordingClicked);
+
         ActivityStarter.OnDismissAction dismissAction = () -> {
-            mHost.getUserContext().startActivity(intent);
+            if (shouldAnimateFromView) {
+                mDialogLaunchAnimator.showFromView(dialog, view);
+            } else {
+                dialog.show();
+            }
             return false;
         };
-        mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false, false);
+
+        mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
+                true /* afterKeyguardDone */);
     }
 
     private void cancelCountdown() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index e6e7e21..e79ca0c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -52,11 +53,11 @@
 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.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIcons;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.wifitrackerlib.WifiEntry;
 
 import java.util.List;
@@ -310,7 +311,7 @@
         final CallbackInfo mInfo = new CallbackInfo();
 
         @Override
-        public void setWifiIndicators(WifiIndicators indicators) {
+        public void setWifiIndicators(@NonNull WifiIndicators indicators) {
             if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + indicators.enabled);
             if (indicators.qsIcon == null) {
                 return;
@@ -332,7 +333,7 @@
     }
 
     protected class WifiDetailAdapter implements DetailAdapter,
-            NetworkController.AccessPointController.AccessPointCallback, QSDetailItems.Callback {
+            AccessPointController.AccessPointCallback, QSDetailItems.Callback {
 
         private QSDetailItems mItems;
         private WifiEntry[] mAccessPoints;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 5673136..1c8bd78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -76,8 +76,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
+import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.toast.SystemUIToast;
@@ -100,7 +99,7 @@
 import javax.inject.Inject;
 
 public class InternetDialogController implements WifiEntry.DisconnectCallback,
-        NetworkController.AccessPointController.AccessPointCallback {
+        AccessPointController.AccessPointCallback {
 
     private static final String TAG = "InternetDialogController";
     private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index 93828b3..79f7ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -63,7 +63,8 @@
                     canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
                     executor)
             if (view != null) {
-                dialogLaunchAnimator.showFromView(internetDialog!!, view)
+                dialogLaunchAnimator.showFromView(internetDialog!!, view,
+                    animateBackgroundBoundsChange = true)
             } else {
                 internetDialog?.show()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 060d7b1..1a08878 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -18,7 +18,6 @@
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -27,10 +26,12 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -44,15 +45,13 @@
 public class RecordingController
         implements CallbackController<RecordingController.RecordingStateChangeCallback> {
     private static final String TAG = "RecordingController";
-    private static final String SYSUI_PACKAGE = "com.android.systemui";
-    private static final String SYSUI_SCREENRECORD_LAUNCHER =
-            "com.android.systemui.screenrecord.ScreenRecordDialog";
 
     private boolean mIsStarting;
     private boolean mIsRecording;
     private PendingIntent mStopIntent;
     private CountDownTimer mCountDownTimer = null;
     private BroadcastDispatcher mBroadcastDispatcher;
+    private UserContextProvider mUserContextProvider;
 
     protected static final String INTENT_UPDATE_STATE =
             "com.android.systemui.screenrecord.UPDATE_STATE";
@@ -88,20 +87,16 @@
      * Create a new RecordingController
      */
     @Inject
-    public RecordingController(BroadcastDispatcher broadcastDispatcher) {
+    public RecordingController(BroadcastDispatcher broadcastDispatcher,
+            UserContextProvider userContextProvider) {
         mBroadcastDispatcher = broadcastDispatcher;
+        mUserContextProvider = userContextProvider;
     }
 
-    /**
-     * Get an intent to show screen recording options to the user.
-     */
-    public Intent getPromptIntent() {
-        final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
-                SYSUI_SCREENRECORD_LAUNCHER);
-        final Intent intent = new Intent();
-        intent.setComponent(launcherComponent);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
+    /** Create a dialog to show screen recording options to the user. */
+    public ScreenRecordDialog createScreenRecordDialog(Context context,
+            @Nullable Runnable onStartRecordingClicked) {
+        return new ScreenRecordDialog(context, this, mUserContextProvider, onStartRecordingClicked);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index df766f3..1fb88df 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.view.Gravity;
-import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.ArrayAdapter;
@@ -34,34 +33,38 @@
 import android.widget.Switch;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.R;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-import javax.inject.Inject;
-
 /**
- * Activity to select screen recording options
+ * Dialog to select screen recording options
  */
-public class ScreenRecordDialog extends Activity {
+public class ScreenRecordDialog extends SystemUIDialog {
+    private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
+            MIC_AND_INTERNAL);
     private static final long DELAY_MS = 3000;
     private static final long INTERVAL_MS = 1000;
-    private static final String TAG = "ScreenRecordDialog";
 
     private final RecordingController mController;
     private final UserContextProvider mUserContextProvider;
+    @Nullable
+    private final Runnable mOnStartRecordingClicked;
     private Switch mTapsSwitch;
     private Switch mAudioSwitch;
     private Spinner mOptions;
-    private List<ScreenRecordingAudioSource> mModes;
 
-    @Inject
-    public ScreenRecordDialog(RecordingController controller,
-            UserContextProvider userContextProvider) {
+    public ScreenRecordDialog(Context context, RecordingController controller,
+            UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) {
+        super(context);
         mController = controller;
         mUserContextProvider = userContextProvider;
+        mOnStartRecordingClicked = onStartRecordingClicked;
     }
 
     @Override
@@ -69,37 +72,35 @@
         super.onCreate(savedInstanceState);
 
         Window window = getWindow();
-        // Inflate the decor view, so the attributes below are not overwritten by the theme.
-        window.getDecorView();
-        window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
         window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
-        window.setGravity(Gravity.TOP);
+
+        window.setGravity(Gravity.CENTER);
         setTitle(R.string.screenrecord_name);
 
         setContentView(R.layout.screen_record_dialog);
 
         TextView cancelBtn = findViewById(R.id.button_cancel);
-        cancelBtn.setOnClickListener(v -> {
-            finish();
-        });
+        cancelBtn.setOnClickListener(v -> dismiss());
 
         TextView startBtn = findViewById(R.id.button_start);
         startBtn.setOnClickListener(v -> {
-            requestScreenCapture();
-            finish();
-        });
+            if (mOnStartRecordingClicked != null) {
+                // Note that it is important to run this callback before dismissing, so that the
+                // callback can disable the dialog exit animation if it wants to.
+                mOnStartRecordingClicked.run();
+            }
 
-        mModes = new ArrayList<>();
-        mModes.add(INTERNAL);
-        mModes.add(MIC);
-        mModes.add(MIC_AND_INTERNAL);
+            requestScreenCapture();
+            dismiss();
+        });
 
         mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
         mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
         mOptions = findViewById(R.id.screen_recording_options);
-        ArrayAdapter a = new ScreenRecordingAdapter(getApplicationContext(),
+        ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
                 android.R.layout.simple_spinner_dropdown_item,
-                mModes);
+                MODES);
         a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
         mOptions.setAdapter(a);
         mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
@@ -116,7 +117,7 @@
         PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
                 RecordingService.REQUEST_CODE,
                 RecordingService.getStartIntent(
-                        userContext, RESULT_OK,
+                        userContext, Activity.RESULT_OK,
                         audioMode.ordinal(), showTaps),
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         PendingIntent stopIntent = PendingIntent.getService(userContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index fca2a18c..fa2ef30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -56,7 +56,8 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -67,6 +68,8 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * Handles keeping track of the current user, profiles, and various things related to hiding
  * contents, redacting notifications, and the lockscreen.
@@ -86,6 +89,8 @@
     // Lazy
     private NotificationEntryManager mEntryManager;
 
+    private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
+    private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
     private final DevicePolicyManager mDevicePolicyManager;
     private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
     private final SparseBooleanArray mUsersWithSeperateWorkChallenge = new SparseBooleanArray();
@@ -162,15 +167,8 @@
                         }
                     }
                     if (notificationKey != null) {
-                        NotificationEntry entry =
-                                getEntryManager().getActiveNotificationUnfiltered(notificationKey);
-                        final int count = getEntryManager().getActiveNotificationsCount();
-                        final int rank = entry != null ? entry.getRanking().getRank() : 0;
-                        NotificationVisibility.NotificationLocation location =
-                                NotificationLogger.getNotificationLocation(entry);
-                        final NotificationVisibility nv = NotificationVisibility.obtain(
-                                notificationKey,
-                                rank, count, true, location);
+                        final NotificationVisibility nv = mVisibilityProviderLazy.get()
+                                .obtain(notificationKey, true);
                         mClickNotifier.onNotificationClick(notificationKey, nv);
                     }
                     break;
@@ -200,6 +198,8 @@
             BroadcastDispatcher broadcastDispatcher,
             DevicePolicyManager devicePolicyManager,
             UserManager userManager,
+            Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
+            Lazy<CommonNotifCollection> commonNotifCollectionLazy,
             NotificationClickNotifier clickNotifier,
             KeyguardManager keyguardManager,
             StatusBarStateController statusBarStateController,
@@ -212,6 +212,8 @@
         mDevicePolicyManager = devicePolicyManager;
         mUserManager = userManager;
         mCurrentUserId = ActivityManager.getCurrentUser();
+        mVisibilityProviderLazy = visibilityProviderLazy;
+        mCommonNotifCollectionLazy = commonNotifCollectionLazy;
         mClickNotifier = clickNotifier;
         statusBarStateController.addCallback(this);
         mLockPatternUtils = new LockPatternUtils(context);
@@ -337,18 +339,18 @@
      * package-specific override.
      */
     public boolean shouldHideNotifications(String key) {
-        if (getEntryManager() == null) {
-            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+        if (mCommonNotifCollectionLazy.get() == null) {
+            Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable());
             return true;
         }
-        NotificationEntry visibleEntry = getEntryManager().getActiveNotificationUnfiltered(key);
+        NotificationEntry visibleEntry = mCommonNotifCollectionLazy.get().getEntry(key);
         return isLockscreenPublicMode(mCurrentUserId) && visibleEntry != null
                 && visibleEntry.getRanking().getLockscreenVisibilityOverride() == VISIBILITY_SECRET;
     }
 
     public boolean shouldShowOnKeyguard(NotificationEntry entry) {
-        if (getEntryManager() == null) {
-            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+        if (mCommonNotifCollectionLazy.get() == null) {
+            Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable());
             return false;
         }
         for (int i = 0; i < mKeyguardSuppressors.size(); i++) {
@@ -520,11 +522,11 @@
     }
 
     private boolean packageHasVisibilityOverride(String key) {
-        if (getEntryManager() == null) {
+        if (mCommonNotifCollectionLazy.get() == null) {
             Log.wtf(TAG, "mEntryManager was null!", new Throwable());
             return true;
         }
-        NotificationEntry entry = getEntryManager().getActiveNotificationUnfiltered(key);
+        NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
         return entry != null
                 && entry.getRanking().getLockscreenVisibilityOverride() 
                 == Notification.VISIBILITY_PRIVATE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index dcb1e4f..7cfa830 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -66,7 +66,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
@@ -113,6 +113,7 @@
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
     }
 
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationEntryManager mEntryManager;
     private final MediaDataManager mMediaDataManager;
     private final NotifPipeline mNotifPipeline;
@@ -181,6 +182,7 @@
             Context context,
             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController,
@@ -201,6 +203,7 @@
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mVisibilityProvider = visibilityProvider;
         mEntryManager = notificationEntryManager;
         mMainExecutor = mainExecutor;
         mMediaDataManager = mediaDataManager;
@@ -351,21 +354,10 @@
     }
 
     private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
-        final int activeNotificationsCount;
-        if (mUsingNotifPipeline) {
-            activeNotificationsCount = mNotifPipeline.getShadeListCount();
-        } else {
-            activeNotificationsCount = mEntryManager.getActiveNotificationsCount();
-        }
         return new DismissedByUserStats(
                 NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA?
                 NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(
-                        entry.getKey(),
-                        entry.getRanking().getRank(),
-                        activeNotificationsCount,
-                        /* visible= */ true,
-                        NotificationLogger.getNotificationLocation(entry)));
+                mVisibilityProvider.obtain(entry, /* visible= */ true));
     }
 
     private void removeEntry(NotificationEntry entry) {
@@ -406,10 +398,7 @@
             return null;
         }
         if (mUsingNotifPipeline) {
-            // TODO(b/169655596): Either add O(1) lookup, or cache this icon?
-            return mNotifPipeline.getAllNotifs().stream()
-                .filter(entry -> Objects.equals(entry.getKey(), mMediaNotificationKey))
-                .findAny()
+            return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey))
                 .map(entry -> entry.getIcons().getShelfIcon())
                 .map(StatusBarIconView::getSourceIcon)
                 .orElse(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 1ce7f03..dd44f72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -60,6 +60,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -95,6 +96,7 @@
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final SmartReplyController mSmartReplyController;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationEntryManager mEntryManager;
     private final Handler mMainHandler;
     private final ActionClickLogger mLogger;
@@ -202,14 +204,7 @@
                 ViewGroup actionGroup = (ViewGroup) parent;
                 buttonIndex = actionGroup.indexOfChild(view);
             }
-            // TODO(b/204183781): get this from the current pipeline
-            final int count = mEntryManager.getActiveNotificationsCount();
-            final int rank = entry.getRanking().getRank();
-
-            NotificationVisibility.NotificationLocation location =
-                    NotificationLogger.getNotificationLocation(entry);
-            final NotificationVisibility nv =
-                    NotificationVisibility.obtain(key, rank, count, true, location);
+            final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
             mClickNotifier.onNotificationActionClick(key, buttonIndex, action, nv, false);
         }
 
@@ -263,6 +258,7 @@
             FeatureFlags featureFlags,
             NotificationLockscreenUserManager lockscreenUserManager,
             SmartReplyController smartReplyController,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager notificationEntryManager,
             RemoteInputNotificationRebuilder rebuilder,
             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
@@ -276,6 +272,7 @@
         mFeatureFlags = featureFlags;
         mLockscreenUserManager = lockscreenUserManager;
         mSmartReplyController = smartReplyController;
+        mVisibilityProvider = visibilityProvider;
         mEntryManager = notificationEntryManager;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mMainHandler = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
index bcba5cc..8a4c4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar;
 
+import android.annotation.NonNull;
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -26,7 +27,9 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.ViewController;
 
@@ -135,10 +138,9 @@
             (area, darkIntensity, tint) ->
                     mView.setTextColor(DarkIconDispatcher.getTint(area, mView, tint));
 
-    private final NetworkController.SignalCallback mSignalCallback =
-            new NetworkController.SignalCallback() {
+    private final SignalCallback mSignalCallback = new SignalCallback() {
         @Override
-        public void setIsAirplaneMode(NetworkController.IconState icon) {
+        public void setIsAirplaneMode(@NonNull IconState icon) {
             update();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index e288b1530..4ad01aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -26,9 +26,8 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -40,7 +39,7 @@
  */
 public class SmartReplyController implements Dumpable {
     private final IStatusBarService mBarService;
-    private final NotificationEntryManager mEntryManager;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationClickNotifier mClickNotifier;
     private final Set<String> mSendingKeys = new ArraySet<>();
     private Callback mCallback;
@@ -50,11 +49,11 @@
      */
     public SmartReplyController(
             DumpManager dumpManager,
-            NotificationEntryManager entryManager,
+            NotificationVisibilityProvider visibilityProvider,
             IStatusBarService statusBarService,
             NotificationClickNotifier clickNotifier) {
         mBarService = statusBarService;
-        mEntryManager = entryManager;
+        mVisibilityProvider = visibilityProvider;
         mClickNotifier = clickNotifier;
         dumpManager.registerDumpable(this);
     }
@@ -84,13 +83,7 @@
     public void smartActionClicked(
             NotificationEntry entry, int actionIndex, Notification.Action action,
             boolean generatedByAssistant) {
-        // TODO(b/204183781): get this from the current pipeline
-        final int count = mEntryManager.getActiveNotificationsCount();
-        final int rank = entry.getRanking().getRank();
-        NotificationVisibility.NotificationLocation location =
-                NotificationLogger.getNotificationLocation(entry);
-        final NotificationVisibility nv = NotificationVisibility.obtain(
-                entry.getKey(), rank, count, true, location);
+        final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
         mClickNotifier.onNotificationActionClick(
                 entry.getKey(), actionIndex, action, nv, generatedByAssistant);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt
new file mode 100644
index 0000000..490994d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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
+
+import android.content.Intent
+import android.os.UserManager
+import android.provider.Settings
+
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+
+/**
+ * Tracks changes in access points.  Allows listening for changes, scanning for new APs,
+ * and connecting to new ones.
+ */
+interface AccessPointController {
+    fun addAccessPointCallback(callback: AccessPointCallback)
+    fun removeAccessPointCallback(callback: AccessPointCallback)
+
+    /**
+     * Request an updated list of available access points
+     *
+     * This method will trigger a call to [AccessPointCallback.onAccessPointsChanged]
+     */
+    fun scanForAccessPoints()
+
+    /**
+     * Gets the current [MergedCarrierEntry]. If null, this call generates a call to
+     * [AccessPointCallback.onAccessPointsChanged]
+     *
+     * @return the current [MergedCarrierEntry], if one exists
+     */
+    fun getMergedCarrierEntry(): MergedCarrierEntry?
+
+    /** @return the appropriate icon id for the given [WifiEntry]'s level */
+    fun getIcon(ap: WifiEntry): Int
+
+    /**
+     * Connects to a [WifiEntry] if it's saved or does not require security.
+     *
+     * If the entry is not saved and requires security, will trigger
+     * [AccessPointCallback.onSettingsActivityTriggered].
+     *
+     * @param ap
+     * @return `true` if [AccessPointCallback.onSettingsActivityTriggered] is triggered
+     */
+    fun connect(ap: WifiEntry?): Boolean
+
+    /**
+     * `true` if the current user does not have the [UserManager.DISALLOW_CONFIG_WIFI] restriction
+     */
+    fun canConfigWifi(): Boolean
+
+    /**
+     * `true` if the current user does not have the [UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS]
+     * restriction set
+     */
+    fun canConfigMobileData(): Boolean
+
+    interface AccessPointCallback {
+        /**
+         * Called whenever [scanForAccessPoints] is called, or [getMergedCarrierEntry] is called
+         * with a null entry
+         *
+         * @param accessPoints the list of available access points, including the current connected
+         * one if it exists
+         */
+        fun onAccessPointsChanged(accessPoints: List<@JvmSuppressWildcards WifiEntry>)
+
+        /**
+         * Called whenever [connecting][connect] to an unknown access point which has security.
+         * Implementers should launch the intent in the appropriate context
+         *
+         * @param settingsIntent an intent for [Settings.ACTION_WIFI_SETTINGS] with
+         * "wifi_start_connect_ssid" set as an extra
+         */
+        fun onSettingsActivityTriggered(settingsIntent: Intent?)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 828a129..7bf710c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -55,9 +55,9 @@
 import javax.inject.Inject;
 
 /** */
-public class AccessPointControllerImpl
-        implements NetworkController.AccessPointController,
-        WifiPickerTracker.WifiPickerTrackerCallback, LifecycleOwner {
+public class AccessPointControllerImpl implements AccessPointController,
+        WifiPickerTracker.WifiPickerTrackerCallback,
+        LifecycleOwner {
     private static final String TAG = "AccessPointController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -116,13 +116,11 @@
         super.finalize();
     }
 
-    /** */
     public boolean canConfigWifi() {
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
                 new UserHandle(mCurrentUser));
     }
 
-    /** */
     public boolean canConfigMobileData() {
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
                 UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin();
@@ -155,7 +153,7 @@
     @Override
     public void scanForAccessPoints() {
         if (mWifiPickerTracker == null) {
-            fireAcccessPointsCallback(Collections.emptyList());
+            fireAccessPointsCallback(Collections.emptyList());
             return;
         }
         List<WifiEntry> entries = mWifiPickerTracker.getWifiEntries();
@@ -163,13 +161,13 @@
         if (connectedEntry != null) {
             entries.add(0, connectedEntry);
         }
-        fireAcccessPointsCallback(entries);
+        fireAccessPointsCallback(entries);
     }
 
     @Override
     public MergedCarrierEntry getMergedCarrierEntry() {
         if (mWifiPickerTracker == null) {
-            fireAcccessPointsCallback(Collections.emptyList());
+            fireAccessPointsCallback(Collections.emptyList());
             return null;
         }
         return mWifiPickerTracker.getMergedCarrierEntry();
@@ -189,7 +187,7 @@
      * @param ap
      * @return {@code true} if {@link AccessPointCallback#onSettingsActivityTriggered} is triggered
      */
-    public boolean connect(WifiEntry ap) {
+    public boolean connect(@Nullable WifiEntry ap) {
         if (ap == null) return false;
         if (DEBUG) {
             if (ap.getWifiConfiguration() != null) {
@@ -221,7 +219,7 @@
         }
     }
 
-    private void fireAcccessPointsCallback(List<WifiEntry> aps) {
+    private void fireAccessPointsCallback(List<WifiEntry> aps) {
         for (AccessPointCallback callback : mCallbacks) {
             callback.onAccessPointsChanged(aps);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
index 052a789..6914ae6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
@@ -23,10 +23,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
new file mode 100644
index 0000000..9c3c10c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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
+
+import android.annotation.SuppressLint
+import com.android.settingslib.SignalIcon.IconGroup
+import java.text.SimpleDateFormat
+
+/**
+ * Base type for various connectivity states, for use with [SignalController] and its subtypes
+ */
+open class ConnectivityState {
+    @JvmField var connected = false
+    @JvmField var enabled = false
+    @JvmField var activityIn = false
+    @JvmField var activityOut = false
+    @JvmField var level = 0
+    @JvmField var iconGroup: IconGroup? = null
+    @JvmField var inetCondition = 0
+    // Only for logging.
+    @JvmField var rssi = 0
+    // Not used for comparison, just used for logging.
+    @JvmField var time: Long = 0
+
+    override fun toString(): String {
+        return if (time != 0L) {
+            val builder = StringBuilder()
+            toString(builder)
+            builder.toString()
+        } else {
+            "Empty " + javaClass.simpleName
+        }
+    }
+
+    protected open fun copyFrom(other: ConnectivityState) {
+        connected = other.connected
+        enabled = other.enabled
+        activityIn = other.activityIn
+        activityOut = other.activityOut
+        level = other.level
+        iconGroup = other.iconGroup
+        inetCondition = other.inetCondition
+        rssi = other.rssi
+        time = other.time
+    }
+
+    protected open fun toString(builder: StringBuilder) {
+        builder.append("connected=$connected,")
+                .append("enabled=$enabled,")
+                .append("level=$level,")
+                .append("inetCondition=$inetCondition,")
+                .append("iconGroup=$iconGroup,")
+                .append("activityIn=$activityIn,")
+                .append("activityOut=$activityOut,")
+                .append("rssi=$rssi,")
+                .append("lastModified=${sSDF.format(time)}")
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other == null) return false
+        if (other.javaClass != javaClass) return false
+
+        val o = other as ConnectivityState
+        return o.connected == connected &&
+                o.enabled == enabled &&
+                o.level == level &&
+                o.inetCondition == inetCondition &&
+                o.iconGroup === iconGroup &&
+                o.activityIn == activityIn &&
+                o.activityOut == activityOut &&
+                o.rssi == rssi
+    }
+
+    override fun hashCode(): Int {
+        var result = connected.hashCode()
+        result = 31 * result + enabled.hashCode()
+        result = 31 * result + activityIn.hashCode()
+        result = 31 * result + activityOut.hashCode()
+        result = 31 * result + level
+        result = 31 * result + (iconGroup?.hashCode() ?: 0)
+        result = 31 * result + inetCondition
+        result = 31 * result + rssi
+        result = 31 * result + time.hashCode()
+        return result
+    }
+}
+
+// No locale as it's only used for logging purposes
+@SuppressLint("SimpleDateFormat")
+private val sSDF = SimpleDateFormat("MM-dd HH:mm:ss.SSS")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java
index c9d40ad..acd9779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java
@@ -20,15 +20,12 @@
 
 import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.SignalIcon.IconGroup;
-import com.android.settingslib.SignalIcon.State;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
 
 import java.util.BitSet;
 
 /** */
 public class EthernetSignalController extends
-        SignalController<State, IconGroup> {
+        SignalController<ConnectivityState, IconGroup> {
 
     public EthernetSignalController(Context context,
             CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
@@ -68,7 +65,7 @@
     }
 
     @Override
-    public State cleanState() {
-        return new State();
+    public ConnectivityState cleanState() {
+        return new ConnectivityState();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index 20ef4ee..9ae7ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -33,7 +33,6 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsMmTelManager;
@@ -47,8 +46,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.SignalIcon.MobileIconGroup;
-import com.android.settingslib.SignalIcon.MobileState;
-import com.android.settingslib.Utils;
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.settingslib.mobile.MobileMappings.Config;
 import com.android.settingslib.mobile.MobileStatusTracker;
@@ -58,9 +55,6 @@
 import com.android.settingslib.net.SignalStrengthUtil;
 import com.android.systemui.R;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
 import com.android.systemui.util.CarrierConfigTracker;
 
 import java.io.PrintWriter;
@@ -93,15 +87,6 @@
     final SubscriptionInfo mSubscriptionInfo;
     private Map<String, MobileIconGroup> mNetworkToIconLookup;
 
-    // Since some pieces of the phone state are interdependent we store it locally,
-    // this could potentially become part of MobileState for simplification/complication
-    // of code.
-    private int mDataState = TelephonyManager.DATA_DISCONNECTED;
-    private TelephonyDisplayInfo mTelephonyDisplayInfo =
-            new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
-    private ServiceState mServiceState;
-    private SignalStrength mSignalStrength;
     private int mLastLevel;
     private MobileIconGroup mDefaultIcons;
     private Config mConfig;
@@ -468,16 +453,8 @@
         return new MobileState();
     }
 
-    private boolean isCdma() {
-        return (mSignalStrength != null) && !mSignalStrength.isGsm();
-    }
-
-    public boolean isEmergencyOnly() {
-        return (mServiceState != null && mServiceState.isEmergencyOnly());
-    }
-
     public boolean isInService() {
-        return Utils.isInService(mServiceState);
+        return mCurrentState.isInService();
     }
 
     String getNetworkNameForCarrierWiFi() {
@@ -485,15 +462,15 @@
     }
 
     private boolean isRoaming() {
-        // During a carrier change, roaming indications need to be supressed.
+        // During a carrier change, roaming indications need to be suppressed.
         if (isCarrierNetworkChangeActive()) {
             return false;
         }
-        if (isCdma()) {
+        if (mCurrentState.isCdma()) {
             return mPhone.getCdmaEnhancedRoamingIndicatorDisplayNumber()
                     != TelephonyManager.ERI_OFF;
         } else {
-            return mServiceState != null && mServiceState.getRoaming();
+            return mCurrentState.isRoaming();
         }
     }
 
@@ -585,27 +562,29 @@
     }
 
     private void updateMobileStatus(MobileStatus mobileStatus) {
-        mCurrentState.activityIn = mobileStatus.activityIn;
-        mCurrentState.activityOut = mobileStatus.activityOut;
-        mCurrentState.dataSim = mobileStatus.dataSim;
-        mCurrentState.carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode;
-        mDataState = mobileStatus.dataState;
+        int lastVoiceState = mCurrentState.getVoiceServiceState();
+        mCurrentState.setFromMobileStatus(mobileStatus);
+
         notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength);
-        mSignalStrength = mobileStatus.signalStrength;
-        mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo;
-        int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1;
-        mServiceState = mobileStatus.serviceState;
-        int currentVoiceState = mServiceState != null ? mServiceState.getState() : -1;
+        if (mProviderModelBehavior) {
+            maybeNotifyCallStateChanged(lastVoiceState);
+        }
+    }
+
+    /** Call state changed is only applicable when provider model behavior is true */
+    private void maybeNotifyCallStateChanged(int lastVoiceState) {
+        int currentVoiceState = mCurrentState.getVoiceServiceState();
+        if (lastVoiceState == currentVoiceState) {
+            return;
+        }
         // Only update the no calling Status in the below scenarios
         // 1. The first valid voice state has been received
         // 2. The voice state has been changed and either the last or current state is
         //    ServiceState.STATE_IN_SERVICE
-        if (mProviderModelBehavior
-                && lastVoiceState != currentVoiceState
-                && (lastVoiceState == -1
-                        || (lastVoiceState == ServiceState.STATE_IN_SERVICE
-                                || currentVoiceState == ServiceState.STATE_IN_SERVICE))) {
-            boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
+        if (lastVoiceState == -1
+                || (lastVoiceState == ServiceState.STATE_IN_SERVICE
+                        || currentVoiceState == ServiceState.STATE_IN_SERVICE)) {
+            boolean isNoCalling = mCurrentState.isNoCalling();
             isNoCalling &= !hideNoCalling();
             IconState statusIcon = new IconState(isNoCalling,
                     R.drawable.ic_qs_no_calling_sms,
@@ -615,7 +594,7 @@
     }
 
     void updateNoCallingState() {
-        int currentVoiceState = mServiceState != null ? mServiceState.getState() : -1;
+        int currentVoiceState = mCurrentState.getVoiceServiceState();
         boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
         isNoCalling &= !hideNoCalling();
         IconState statusIcon = new IconState(isNoCalling,
@@ -643,8 +622,7 @@
     }
 
     void refreshCallIndicator(SignalCallback callback) {
-        boolean isNoCalling = mServiceState != null
-                && mServiceState.getState() != ServiceState.STATE_IN_SERVICE;
+        boolean isNoCalling = mCurrentState.isNoCalling();
         isNoCalling &= !hideNoCalling();
         IconState statusIcon = new IconState(isNoCalling,
                 R.drawable.ic_qs_no_calling_sms,
@@ -736,30 +714,30 @@
     }
 
     /**
-     * Updates the current state based on mServiceState, mSignalStrength, mDataState,
-     * mTelephonyDisplayInfo, and mSimState.  It should be called any time one of these is updated.
+     * Updates the current state based on ServiceState, SignalStrength, DataState,
+     * TelephonyDisplayInfo, and sim state.  It should be called any time one of these is updated.
      * This will call listeners if necessary.
      */
     private void updateTelephony() {
         if (Log.isLoggable(mTag, Log.DEBUG)) {
             Log.d(mTag, "updateTelephonySignalStrength: hasService="
-                    + Utils.isInService(mServiceState) + " ss=" + mSignalStrength
-                    + " displayInfo=" + mTelephonyDisplayInfo);
+                    + mCurrentState.isInService()
+                    + " ss=" + mCurrentState.signalStrength
+                    + " displayInfo=" + mCurrentState.telephonyDisplayInfo);
         }
         checkDefaultData();
-        mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null;
+        mCurrentState.connected = mCurrentState.isInService();
         if (mCurrentState.connected) {
-            mCurrentState.level = getSignalLevel(mSignalStrength);
+            mCurrentState.level = getSignalLevel(mCurrentState.signalStrength);
         }
 
-        String iconKey = getIconKey(mTelephonyDisplayInfo);
+        String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo);
         if (mNetworkToIconLookup.get(iconKey) != null) {
             mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey);
         } else {
             mCurrentState.iconGroup = mDefaultIcons;
         }
-        mCurrentState.dataConnected = mCurrentState.connected
-                && mDataState == TelephonyManager.DATA_CONNECTED;
+        mCurrentState.dataConnected = mCurrentState.isDataConnected();
 
         mCurrentState.roaming = isRoaming();
         if (isCarrierNetworkChangeActive()) {
@@ -771,20 +749,20 @@
                 mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
             }
         }
-        if (isEmergencyOnly() != mCurrentState.isEmergency) {
-            mCurrentState.isEmergency = isEmergencyOnly();
+        if (mCurrentState.isEmergencyOnly() != mCurrentState.isEmergency) {
+            mCurrentState.isEmergency = mCurrentState.isEmergencyOnly();
             mNetworkController.recalculateEmergency();
         }
         // Fill in the network name if we think we have it.
-        if (mCurrentState.networkName.equals(mNetworkNameDefault) && mServiceState != null
-                && !TextUtils.isEmpty(mServiceState.getOperatorAlphaShort())) {
-            mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
+        if (mCurrentState.networkName.equals(mNetworkNameDefault)
+                && !TextUtils.isEmpty(mCurrentState.getOperatorAlphaShort())) {
+            mCurrentState.networkName = mCurrentState.getOperatorAlphaShort();
         }
         // If this is the data subscription, update the currentState data name
-        if (mCurrentState.networkNameData.equals(mNetworkNameDefault) && mServiceState != null
+        if (mCurrentState.networkNameData.equals(mNetworkNameDefault)
                 && mCurrentState.dataSim
-                && !TextUtils.isEmpty(mServiceState.getOperatorAlphaShort())) {
-            mCurrentState.networkNameData = mServiceState.getOperatorAlphaShort();
+                && !TextUtils.isEmpty(mCurrentState.getOperatorAlphaShort())) {
+            mCurrentState.networkNameData = mCurrentState.getOperatorAlphaShort();
         }
 
         notifyListenersIfNecessary();
@@ -836,10 +814,6 @@
         pw.println("  mSubscription=" + mSubscriptionInfo + ",");
         pw.println("  mProviderModelSetting=" + mProviderModelSetting + ",");
         pw.println("  mProviderModelBehavior=" + mProviderModelBehavior + ",");
-        pw.println("  mServiceState=" + mServiceState + ",");
-        pw.println("  mSignalStrength=" + mSignalStrength + ",");
-        pw.println("  mTelephonyDisplayInfo=" + mTelephonyDisplayInfo + ",");
-        pw.println("  mDataState=" + mDataState + ",");
         pw.println("  mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
         pw.println("  isDataDisabled=" + isDataDisabled() + ",");
         pw.println("  mNetworkToIconLookup=" + mNetworkToIconLookup + ",");
@@ -884,5 +858,4 @@
             icon = iconState;
         }
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
new file mode 100644
index 0000000..8a3b006
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2021 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
+
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import com.android.settingslib.Utils
+import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus
+import com.android.settingslib.mobile.TelephonyIcons
+import java.lang.IllegalArgumentException
+
+/**
+ * Box for all policy-related state used in [MobileSignalController]
+ */
+internal class MobileState(
+    @JvmField var networkName: String? = null,
+    @JvmField var networkNameData: String? = null,
+    @JvmField var dataSim: Boolean = false,
+    @JvmField var dataConnected: Boolean = false,
+    @JvmField var isEmergency: Boolean = false,
+    @JvmField var airplaneMode: Boolean = false,
+    @JvmField var carrierNetworkChangeMode: Boolean = false,
+    @JvmField var isDefault: Boolean = false,
+    @JvmField var userSetup: Boolean = false,
+    @JvmField var roaming: Boolean = false,
+    @JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED,
+    // Tracks the on/off state of the defaultDataSubscription
+    @JvmField var defaultDataOff: Boolean = false
+) : ConnectivityState() {
+
+    @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE)
+    @JvmField var serviceState: ServiceState? = null
+    @JvmField var signalStrength: SignalStrength? = null
+
+    /** @return true if this state is disabled or not default data */
+    val isDataDisabledOrNotDefault: Boolean
+        get() = (iconGroup === TelephonyIcons.DATA_DISABLED ||
+                iconGroup === TelephonyIcons.NOT_DEFAULT_DATA) && userSetup
+
+    /** @return if this state is considered to have inbound activity */
+    fun hasActivityIn(): Boolean {
+        return dataConnected && !carrierNetworkChangeMode && activityIn
+    }
+
+    /** @return if this state is considered to have outbound activity */
+    fun hasActivityOut(): Boolean {
+        return dataConnected && !carrierNetworkChangeMode && activityOut
+    }
+
+    /** @return true if this state should show a RAT icon in quick settings */
+    fun showQuickSettingsRatIcon(): Boolean {
+        return dataConnected || isDataDisabledOrNotDefault
+    }
+
+    override fun copyFrom(other: ConnectivityState) {
+        val o = other as? MobileState ?: throw IllegalArgumentException(
+                "MobileState can only update from another MobileState")
+
+        super.copyFrom(o)
+        networkName = o.networkName
+        networkNameData = o.networkNameData
+        dataSim = o.dataSim
+        dataConnected = o.dataConnected
+        isEmergency = o.isEmergency
+        airplaneMode = o.airplaneMode
+        carrierNetworkChangeMode = o.carrierNetworkChangeMode
+        isDefault = o.isDefault
+        userSetup = o.userSetup
+        roaming = o.roaming
+        dataState = o.dataState
+        defaultDataOff = o.defaultDataOff
+
+        telephonyDisplayInfo = o.telephonyDisplayInfo
+        serviceState = o.serviceState
+        signalStrength = o.signalStrength
+    }
+
+    fun isDataConnected(): Boolean {
+        return connected && dataState == TelephonyManager.DATA_CONNECTED
+    }
+
+    /** @return the current voice service state, or -1 if null */
+    fun getVoiceServiceState(): Int {
+        return serviceState?.state ?: -1
+    }
+
+    fun isNoCalling(): Boolean {
+        return serviceState?.state != ServiceState.STATE_IN_SERVICE
+    }
+
+    fun getOperatorAlphaShort(): String {
+        return serviceState?.operatorAlphaShort ?: ""
+    }
+
+    fun isCdma(): Boolean {
+        return signalStrength != null && !signalStrength!!.isGsm
+    }
+
+    fun isEmergencyOnly(): Boolean {
+        return serviceState != null && serviceState!!.isEmergencyOnly
+    }
+
+    fun isInService(): Boolean {
+        return Utils.isInService(serviceState)
+    }
+
+    fun isRoaming(): Boolean {
+        return serviceState != null && serviceState!!.roaming
+    }
+
+    fun setFromMobileStatus(mobileStatus: MobileStatus) {
+        activityIn = mobileStatus.activityIn
+        activityOut = mobileStatus.activityOut
+        dataSim = mobileStatus.dataSim
+        carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode
+        dataState = mobileStatus.dataState
+        signalStrength = mobileStatus.signalStrength
+        telephonyDisplayInfo = mobileStatus.telephonyDisplayInfo
+        serviceState = mobileStatus.serviceState
+    }
+
+    override fun toString(builder: StringBuilder) {
+        super.toString(builder)
+        builder.append(',')
+        builder.append("dataSim=$dataSim,")
+        builder.append("networkName=$networkName,")
+        builder.append("networkNameData=$networkNameData,")
+        builder.append("dataConnected=$dataConnected,")
+        builder.append("roaming=$roaming,")
+        builder.append("isDefault=$isDefault,")
+        builder.append("isEmergency=$isEmergency,")
+        builder.append("airplaneMode=$airplaneMode,")
+        builder.append("carrierNetworkChangeMode=$carrierNetworkChangeMode,")
+        builder.append("userSetup=$userSetup,")
+        builder.append("dataState=$dataState,")
+        builder.append("defaultDataOff=$defaultDataOff,")
+
+        // Computed properties
+        builder.append("showQuickSettingsRatIcon=${showQuickSettingsRatIcon()},")
+        builder.append("voiceServiceState=${getVoiceServiceState()},")
+        builder.append("isInService=${isInService()},")
+
+        builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},")
+        builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},")
+        builder.append("displayInfo=$telephonyDisplayInfo")
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+        if (!super.equals(other)) return false
+
+        other as MobileState
+
+        if (networkName != other.networkName) return false
+        if (networkNameData != other.networkNameData) return false
+        if (dataSim != other.dataSim) return false
+        if (dataConnected != other.dataConnected) return false
+        if (isEmergency != other.isEmergency) return false
+        if (airplaneMode != other.airplaneMode) return false
+        if (carrierNetworkChangeMode != other.carrierNetworkChangeMode) return false
+        if (isDefault != other.isDefault) return false
+        if (userSetup != other.userSetup) return false
+        if (roaming != other.roaming) return false
+        if (dataState != other.dataState) return false
+        if (defaultDataOff != other.defaultDataOff) return false
+        if (telephonyDisplayInfo != other.telephonyDisplayInfo) return false
+        if (serviceState != other.serviceState) return false
+        if (signalStrength != other.signalStrength) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + (networkName?.hashCode() ?: 0)
+        result = 31 * result + (networkNameData?.hashCode() ?: 0)
+        result = 31 * result + dataSim.hashCode()
+        result = 31 * result + dataConnected.hashCode()
+        result = 31 * result + isEmergency.hashCode()
+        result = 31 * result + airplaneMode.hashCode()
+        result = 31 * result + carrierNetworkChangeMode.hashCode()
+        result = 31 * result + isDefault.hashCode()
+        result = 31 * result + userSetup.hashCode()
+        result = 31 * result + roaming.hashCode()
+        result = 31 * result + dataState
+        result = 31 * result + defaultDataOff.hashCode()
+        result = 31 * result + telephonyDisplayInfo.hashCode()
+        result = 31 * result + (serviceState?.hashCode() ?: 0)
+        result = 31 * result + (signalStrength?.hashCode() ?: 0)
+        return result
+    }
+}
+
+/** toString() is a little more verbose than we need. Just log the fields we read */
+private fun ServiceState.minLog(): String {
+    return "serviceState={" +
+            "state=$state," +
+            "isEmergencyOnly=$isEmergencyOnly," +
+            "roaming=$roaming," +
+            "operatorNameAlphaShort=$operatorAlphaShort}"
+}
+
+private fun SignalStrength.minLog(): String {
+    return "signalStrength={" +
+            "isGsm=$isGsm," +
+            "level=$level}"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
index 1433096..f960eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
@@ -16,19 +16,10 @@
 
 package com.android.systemui.statusbar.connectivity;
 
-import android.content.Context;
-import android.content.Intent;
-import android.telephony.SubscriptionInfo;
-
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.wifitrackerlib.MergedCarrierEntry;
-import com.android.wifitrackerlib.WifiEntry;
-
-import java.util.List;
 
 /** */
 public interface NetworkController extends CallbackController<SignalCallback>, DemoMode {
@@ -62,197 +53,8 @@
     /** */
     boolean isRadioOn();
 
-    /**
-     * Wrapper class for all the WiFi signals used for WiFi indicators.
-     */
-    final class WifiIndicators {
-        public boolean enabled;
-        public IconState statusIcon;
-        public IconState qsIcon;
-        public boolean activityIn;
-        public boolean activityOut;
-        public String description;
-        public boolean isTransient;
-        public String statusLabel;
-
-        public WifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
-                boolean activityIn, boolean activityOut, String description,
-                boolean isTransient, String statusLabel) {
-            this.enabled = enabled;
-            this.statusIcon = statusIcon;
-            this.qsIcon = qsIcon;
-            this.activityIn = activityIn;
-            this.activityOut = activityOut;
-            this.description = description;
-            this.isTransient = isTransient;
-            this.statusLabel = statusLabel;
-        }
-
-        @Override
-        public String toString() {
-            return new StringBuilder("WifiIndicators[")
-                .append("enabled=").append(enabled)
-                .append(",statusIcon=").append(statusIcon == null ? "" : statusIcon.toString())
-                .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString())
-                .append(",activityIn=").append(activityIn)
-                .append(",activityOut=").append(activityOut)
-                .append(",qsDescription=").append(description)
-                .append(",isTransient=").append(isTransient)
-                .append(",statusLabel=").append(statusLabel)
-                .append(']').toString();
-        }
-    }
-
-    /**
-     * Wrapper class for all the mobile signals used for mobile data indicators.
-     */
-    final class MobileDataIndicators {
-        public IconState statusIcon;
-        public IconState qsIcon;
-        public int statusType;
-        public int qsType;
-        public boolean activityIn;
-        public boolean activityOut;
-        public CharSequence typeContentDescription;
-        public CharSequence typeContentDescriptionHtml;
-        public CharSequence qsDescription;
-        public int subId;
-        public boolean roaming;
-        public boolean showTriangle;
-
-        public MobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
-                int qsType, boolean activityIn, boolean activityOut,
-                CharSequence typeContentDescription, CharSequence typeContentDescriptionHtml,
-                CharSequence qsDescription, int subId, boolean roaming,
-                boolean showTriangle) {
-            this.statusIcon = statusIcon;
-            this.qsIcon = qsIcon;
-            this.statusType = statusType;
-            this.qsType = qsType;
-            this.activityIn = activityIn;
-            this.activityOut = activityOut;
-            this.typeContentDescription = typeContentDescription;
-            this.typeContentDescriptionHtml = typeContentDescriptionHtml;
-            this.qsDescription = qsDescription;
-            this.subId = subId;
-            this.roaming = roaming;
-            this.showTriangle = showTriangle;
-        }
-
-        @Override
-        public String toString() {
-            return new StringBuilder("MobileDataIndicators[")
-                .append("statusIcon=").append(statusIcon == null ? "" :  statusIcon.toString())
-                .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString())
-                .append(",statusType=").append(statusType)
-                .append(",qsType=").append(qsType)
-                .append(",activityIn=").append(activityIn)
-                .append(",activityOut=").append(activityOut)
-                .append(",typeContentDescription=").append(typeContentDescription)
-                .append(",typeContentDescriptionHtml=").append(typeContentDescriptionHtml)
-                .append(",description=").append(qsDescription)
-                .append(",subId=").append(subId)
-                .append(",roaming=").append(roaming)
-                .append(",showTriangle=").append(showTriangle)
-                .append(']').toString();
-        }
-    }
-
-    /** */
-    interface SignalCallback {
-        /**
-         * Callback for listeners to be able to update the state of any UI tracking connectivity of
-         * WiFi networks.
-         */
-        default void setWifiIndicators(WifiIndicators wifiIndicators) {}
-
-        /**
-         * Callback for listeners to be able to update the state of any UI tracking connectivity
-         * of Mobile networks.
-         */
-        default void setMobileDataIndicators(MobileDataIndicators mobileDataIndicators) {}
-
-        /** */
-        default void setSubs(List<SubscriptionInfo> subs) {}
-
-        /** */
-        default void setNoSims(boolean show, boolean simDetected) {}
-
-        /** */
-        default void setEthernetIndicators(IconState icon) {}
-
-        /** */
-        default void setIsAirplaneMode(IconState icon) {}
-
-        /** */
-        default void setMobileDataEnabled(boolean enabled) {}
-
-        /**
-         * Callback for listeners to be able to update the connectivity status
-         * @param noDefaultNetwork whether there is any default network.
-         * @param noValidatedNetwork whether there is any validated network.
-         * @param noNetworksAvailable whether there is any WiFi networks available.
-         */
-        default void setConnectivityStatus(boolean noDefaultNetwork, boolean noValidatedNetwork,
-                boolean noNetworksAvailable) {}
-
-        /**
-         * Callback for listeners to be able to update the call indicator
-         * @param statusIcon the icon for the call indicator
-         * @param subId subscription ID for which to update the UI
-         */
-        default void setCallIndicator(IconState statusIcon, int subId) {}
-    }
-
     /** */
     interface EmergencyListener {
         void setEmergencyCallsOnly(boolean emergencyOnly);
     }
-
-    /** */
-    class IconState {
-        public final boolean visible;
-        public final int icon;
-        public final String contentDescription;
-
-        public IconState(boolean visible, int icon, String contentDescription) {
-            this.visible = visible;
-            this.icon = icon;
-            this.contentDescription = contentDescription;
-        }
-
-        public IconState(boolean visible, int icon, int contentDescription,
-                Context context) {
-            this(visible, icon, context.getString(contentDescription));
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder builder = new StringBuilder();
-            return builder.append("[visible=").append(visible).append(',')
-                .append("icon=").append(icon).append(',')
-                .append("contentDescription=").append(contentDescription).append(']')
-                .toString();
-        }
-    }
-
-    /**
-     * Tracks changes in access points.  Allows listening for changes, scanning for new APs,
-     * and connecting to new ones.
-     */
-    interface AccessPointController {
-        void addAccessPointCallback(AccessPointCallback callback);
-        void removeAccessPointCallback(AccessPointCallback callback);
-        void scanForAccessPoints();
-        MergedCarrierEntry getMergedCarrierEntry();
-        int getIcon(WifiEntry ap);
-        boolean connect(WifiEntry ap);
-        boolean canConfigWifi();
-        boolean canConfigMobileData();
-
-        interface AccessPointCallback {
-            void onAccessPointsChanged(List<WifiEntry> accessPoints);
-            void onSettingsActivityTriggered(Intent settingsIntent);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index a4a648e..26780eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -721,8 +721,11 @@
     @Override
     public void addCallback(@NonNull SignalCallback cb) {
         cb.setSubs(mCurrentSubscriptions);
-        cb.setIsAirplaneMode(new IconState(mAirplaneMode,
-                TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+        cb.setIsAirplaneMode(
+                new IconState(
+                        mAirplaneMode,
+                        TelephonyIcons.FLIGHT_MODE_ICON,
+                        mContext.getString(R.string.accessibility_airplane_mode)));
         cb.setNoSims(mHasNoSubs, mSimDetected);
         if (mProviderModelSetting) {
             cb.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition, mNoNetworksAvailable);
@@ -1056,8 +1059,11 @@
      * notifyAllListeners.
      */
     private void notifyListeners() {
-        mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
-                TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+        mCallbackHandler.setIsAirplaneMode(
+                new IconState(
+                        mAirplaneMode,
+                        TelephonyIcons.FLIGHT_MODE_ICON,
+                        mContext.getString(R.string.accessibility_airplane_mode)));
         mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
     }
 
@@ -1206,7 +1212,7 @@
     }
 
     private boolean mDemoInetCondition;
-    private WifiSignalController.WifiState mDemoWifiState;
+    private WifiState mDemoWifiState;
 
     @Override
     public void onDemoModeStarted() {
@@ -1241,9 +1247,11 @@
         String airplane = args.getString("airplane");
         if (airplane != null) {
             boolean show = airplane.equals("show");
-            mCallbackHandler.setIsAirplaneMode(new IconState(show,
-                    TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
-                    mContext));
+            mCallbackHandler.setIsAirplaneMode(
+                    new IconState(
+                            show,
+                            TelephonyIcons.FLIGHT_MODE_ICON,
+                            mContext.getString(R.string.accessibility_airplane_mode)));
         }
         String fully = args.getString("fully");
         if (fully != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt
new file mode 100644
index 0000000..599beec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2021 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
+
+import android.telephony.SubscriptionInfo
+
+/**
+ * SignalCallback contains all of the connectivity updates from [NetworkController]. Implement this
+ * interface to be able to draw iconography for Wi-Fi, mobile data, ethernet, call strength
+ * indicators, etc.
+ */
+interface SignalCallback {
+    /**
+     * Called when the Wi-Fi iconography has been updated. Implement this method to draw Wi-Fi icons
+     *
+     * @param wifiIndicators a box type containing enough information to properly draw a Wi-Fi icon
+     */
+    @JvmDefault
+    fun setWifiIndicators(wifiIndicators: WifiIndicators) {}
+
+    /**
+     * Called when the mobile iconography has been updated. Implement this method to draw mobile
+     * indicators
+     *
+     * @param mobileDataIndicators a box type containing enough information to properly draw
+     * mobile data icons
+     *
+     * NOTE: phones can have multiple subscriptions, so this [mobileDataIndicators] object should be
+     * indexed based on its [subId][MobileDataIndicators.subId]
+     */
+    @JvmDefault
+    fun setMobileDataIndicators(mobileDataIndicators: MobileDataIndicators) {}
+
+    /**
+     * Called when the list of mobile data subscriptions has changed. Use this method as a chance
+     * to remove views that are no longer needed, or to make room for new icons to come in
+     *
+     * @param subs a [SubscriptionInfo] for each subscription that we know about
+     */
+    @JvmDefault
+    fun setSubs(subs: List<@JvmSuppressWildcards SubscriptionInfo>) {}
+
+    /**
+     * Called when:
+     * 1. The number of [MobileSignalController]s goes to 0 while mobile data is enabled
+     * OR
+     * 2. The presence of any SIM changes
+     *
+     * @param show whether or not to show a "no sim" view
+     * @param simDetected whether any SIM is detected or not
+     */
+    @JvmDefault
+    fun setNoSims(show: Boolean, simDetected: Boolean) {}
+
+    /**
+     * Called when there is any update to the ethernet iconography. Implement this method to set an
+     * ethernet icon
+     *
+     * @param icon an [IconState] for the current ethernet status
+     */
+    @JvmDefault
+    fun setEthernetIndicators(icon: IconState) {}
+
+    /**
+     * Called whenever airplane mode changes
+     *
+     * @param icon an [IconState] for the current airplane mode status
+     */
+    @JvmDefault
+    fun setIsAirplaneMode(icon: IconState) {}
+
+    /**
+     * Called whenever the mobile data feature enabled state changes
+     *
+     * @param enabled the current mobile data feature ennabled state
+     */
+    @JvmDefault
+    fun setMobileDataEnabled(enabled: Boolean) {}
+
+    /**
+     * Callback for listeners to be able to update the connectivity status
+     * @param noDefaultNetwork whether there is any default network.
+     * @param noValidatedNetwork whether there is any validated network.
+     * @param noNetworksAvailable whether there is any WiFi networks available.
+     */
+    @JvmDefault
+    fun setConnectivityStatus(
+        noDefaultNetwork: Boolean,
+        noValidatedNetwork: Boolean,
+        noNetworksAvailable: Boolean
+    ) { }
+
+    /**
+     * Callback for listeners to be able to update the call indicator
+     * @param statusIcon the icon for the call indicator
+     * @param subId subscription ID for which to update the UI
+     */
+    @JvmDefault
+    fun setCallIndicator(statusIcon: IconState, subId: Int) {}
+}
+
+/** Box type for [SignalCallback.setWifiIndicators] */
+data class WifiIndicators(
+    @JvmField val enabled: Boolean,
+    @JvmField val statusIcon: IconState?,
+    @JvmField val qsIcon: IconState?,
+    @JvmField val activityIn: Boolean,
+    @JvmField val activityOut: Boolean,
+    @JvmField val description: String?,
+    @JvmField val isTransient: Boolean,
+    @JvmField val statusLabel: String?
+) {
+    override fun toString(): String {
+        return StringBuilder("WifiIndicators[")
+                .append("enabled=").append(enabled)
+                .append(",statusIcon=").append(statusIcon?.toString() ?: "")
+                .append(",qsIcon=").append(qsIcon?.toString() ?: "")
+                .append(",activityIn=").append(activityIn)
+                .append(",activityOut=").append(activityOut)
+                .append(",qsDescription=").append(description)
+                .append(",isTransient=").append(isTransient)
+                .append(",statusLabel=").append(statusLabel)
+                .append(']').toString()
+    }
+}
+
+/** Box type for [SignalCallback.setMobileDataIndicators] */
+data class MobileDataIndicators(
+    @JvmField val statusIcon: IconState?,
+    @JvmField val qsIcon: IconState?,
+    @JvmField val statusType: Int,
+    @JvmField val qsType: Int,
+    @JvmField val activityIn: Boolean,
+    @JvmField val activityOut: Boolean,
+    @JvmField val typeContentDescription: CharSequence?,
+    @JvmField val typeContentDescriptionHtml: CharSequence?,
+    @JvmField val qsDescription: CharSequence?,
+    @JvmField val subId: Int,
+    @JvmField val roaming: Boolean,
+    @JvmField val showTriangle: Boolean
+) {
+    override fun toString(): String {
+        return java.lang.StringBuilder("MobileDataIndicators[")
+                .append("statusIcon=").append(statusIcon?.toString() ?: "")
+                .append(",qsIcon=").append(qsIcon?.toString() ?: "")
+                .append(",statusType=").append(statusType)
+                .append(",qsType=").append(qsType)
+                .append(",activityIn=").append(activityIn)
+                .append(",activityOut=").append(activityOut)
+                .append(",typeContentDescription=").append(typeContentDescription)
+                .append(",typeContentDescriptionHtml=").append(typeContentDescriptionHtml)
+                .append(",description=").append(qsDescription)
+                .append(",subId=").append(subId)
+                .append(",roaming=").append(roaming)
+                .append(",showTriangle=").append(showTriangle)
+                .append(']').toString()
+    }
+}
+
+/** Box for an icon with its visibility and content description */
+data class IconState(
+    @JvmField val visible: Boolean,
+    @JvmField val icon: Int,
+    @JvmField val contentDescription: String
+) {
+    override fun toString(): String {
+        val builder = java.lang.StringBuilder()
+        return builder.append("[visible=").append(visible).append(',')
+                .append("icon=").append(icon).append(',')
+                .append("contentDescription=").append(contentDescription).append(']')
+                .toString()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
index d23dba5..cd20068 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
@@ -22,9 +22,6 @@
 import android.util.Log;
 
 import com.android.settingslib.SignalIcon.IconGroup;
-import com.android.settingslib.SignalIcon.State;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
 
 import java.io.PrintWriter;
 import java.util.BitSet;
@@ -36,7 +33,7 @@
  * @param <T> State of the SysUI controller.
  * @param <I> Icon groups of the SysUI controller for a given State.
  */
-public abstract class SignalController<T extends State, I extends IconGroup> {
+public abstract class SignalController<T extends ConnectivityState, I extends IconGroup> {
     // Save the previous SignalController.States of all SignalControllers for dumps.
     static final boolean RECORD_HISTORY = true;
     // If RECORD_HISTORY how many to save, must be a power of 2.
@@ -58,7 +55,7 @@
     private final CallbackHandler mCallbackHandler;
 
     // Save the previous HISTORY_SIZE states for logging.
-    private final State[] mHistory;
+    private final ConnectivityState[] mHistory;
     // Where to copy the next state into.
     private int mHistoryIndex;
 
@@ -72,7 +69,7 @@
         mCurrentState = cleanState();
         mLastState = cleanState();
         if (RECORD_HISTORY) {
-            mHistory = new State[HISTORY_SIZE];
+            mHistory = new ConnectivityState[HISTORY_SIZE];
             for (int i = 0; i < HISTORY_SIZE; i++) {
                 mHistory[i] = cleanState();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index 3622a66..103ca0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -26,28 +26,20 @@
 import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.text.Html;
-import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.SignalIcon.IconGroup;
 import com.android.settingslib.SignalIcon.MobileIconGroup;
-import com.android.settingslib.SignalIcon.State;
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.wifi.WifiStatusTracker;
 import com.android.systemui.R;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
 
 import java.io.PrintWriter;
-import java.util.Objects;
 
 /** */
-public class WifiSignalController extends
-        SignalController<WifiSignalController.WifiState, IconGroup> {
+public class WifiSignalController extends SignalController<WifiState, IconGroup> {
     private final boolean mHasMobileDataFeature;
     private final WifiStatusTracker mWifiTracker;
     private final IconGroup mUnmergedWifiIconGroup = WifiIcons.UNMERGED_WIFI;
@@ -272,50 +264,4 @@
             setActivity(state);
         }
     }
-
-    static class WifiState extends State {
-        public String ssid;
-        public boolean isTransient;
-        public boolean isDefault;
-        public String statusLabel;
-        public boolean isCarrierMerged;
-        public int subId;
-
-        @Override
-        public void copyFrom(State s) {
-            super.copyFrom(s);
-            WifiState state = (WifiState) s;
-            ssid = state.ssid;
-            isTransient = state.isTransient;
-            isDefault = state.isDefault;
-            statusLabel = state.statusLabel;
-            isCarrierMerged = state.isCarrierMerged;
-            subId = state.subId;
-        }
-
-        @Override
-        protected void toString(StringBuilder builder) {
-            super.toString(builder);
-            builder.append(",ssid=").append(ssid)
-                .append(",isTransient=").append(isTransient)
-                .append(",isDefault=").append(isDefault)
-                .append(",statusLabel=").append(statusLabel)
-                .append(",isCarrierMerged=").append(isCarrierMerged)
-                .append(",subId=").append(subId);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!super.equals(o)) {
-                return false;
-            }
-            WifiState other = (WifiState) o;
-            return Objects.equals(other.ssid, ssid)
-                    && other.isTransient == isTransient
-                    && other.isDefault == isDefault
-                    && TextUtils.equals(other.statusLabel, statusLabel)
-                    && other.isCarrierMerged == isCarrierMerged
-                    && other.subId == subId;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
new file mode 100644
index 0000000..ac15f78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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
+
+import java.lang.StringBuilder
+
+internal class WifiState(
+    @JvmField var ssid: String? = null,
+    @JvmField var isTransient: Boolean = false,
+    @JvmField var isDefault: Boolean = false,
+    @JvmField var statusLabel: String? = null,
+    @JvmField var isCarrierMerged: Boolean = false,
+    @JvmField var subId: Int = 0
+) : ConnectivityState() {
+
+    public override fun copyFrom(s: ConnectivityState) {
+        super.copyFrom(s)
+        val state = s as WifiState
+        ssid = state.ssid
+        isTransient = state.isTransient
+        isDefault = state.isDefault
+        statusLabel = state.statusLabel
+        isCarrierMerged = state.isCarrierMerged
+        subId = state.subId
+    }
+
+    override fun toString(builder: StringBuilder) {
+        super.toString(builder)
+        builder.append(",ssid=").append(ssid)
+                .append(",isTransient=").append(isTransient)
+                .append(",isDefault=").append(isDefault)
+                .append(",statusLabel=").append(statusLabel)
+                .append(",isCarrierMerged=").append(isCarrierMerged)
+                .append(",subId=").append(subId)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+        if (!super.equals(other)) return false
+
+        other as WifiState
+
+        if (ssid != other.ssid) return false
+        if (isTransient != other.isTransient) return false
+        if (isDefault != other.isDefault) return false
+        if (statusLabel != other.statusLabel) return false
+        if (isCarrierMerged != other.isCarrierMerged) return false
+        if (subId != other.subId) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + (ssid?.hashCode() ?: 0)
+        result = 31 * result + isTransient.hashCode()
+        result = 31 * result + isDefault.hashCode()
+        result = 31 * result + (statusLabel?.hashCode() ?: 0)
+        result = 31 * result + isCarrierMerged.hashCode()
+        result = 31 * result + subId
+        return result
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 74ea19f..aa86daa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -59,6 +59,7 @@
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -102,6 +103,7 @@
             FeatureFlags featureFlags,
             NotificationLockscreenUserManager lockscreenUserManager,
             SmartReplyController smartReplyController,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager notificationEntryManager,
             RemoteInputNotificationRebuilder rebuilder,
             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
@@ -116,6 +118,7 @@
                 featureFlags,
                 lockscreenUserManager,
                 smartReplyController,
+                visibilityProvider,
                 notificationEntryManager,
                 rebuilder,
                 statusBarOptionalLazy,
@@ -134,6 +137,7 @@
             Context context,
             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController,
@@ -147,6 +151,7 @@
                 context,
                 statusBarOptionalLazy,
                 notificationShadeWindowController,
+                visibilityProvider,
                 notificationEntryManager,
                 mediaArtworkProcessor,
                 keyguardBypassController,
@@ -174,10 +179,14 @@
     @Provides
     static SmartReplyController provideSmartReplyController(
             DumpManager dumpManager,
-            NotificationEntryManager entryManager,
+            NotificationVisibilityProvider visibilityProvider,
             IStatusBarService statusBarService,
             NotificationClickNotifier clickNotifier) {
-        return new SmartReplyController(dumpManager, entryManager, statusBarService, clickNotifier);
+        return new SmartReplyController(
+                dumpManager,
+                visibilityProvider,
+                statusBarService,
+                clickNotifier);
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 8bc41c2..82f35a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -721,8 +721,12 @@
      * @param reason why the notifications are updating
      */
     public void updateNotifications(String reason) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
+            return;
+        }
         reapplyFilterAndSort(reason);
-        if (mPresenter != null && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+        if (mPresenter != null) {
             mPresenter.updateNotificationViews(reason);
         }
     }
@@ -808,11 +812,11 @@
      * notification doesn't exist.
      */
     public NotificationEntry getPendingOrActiveNotif(String key) {
-        if (mPendingNotifications.containsKey(key)) {
-            return mPendingNotifications.get(key);
-        } else {
-            return mActiveNotifications.get(key);
+        NotificationEntry entry = mPendingNotifications.get(key);
+        if (entry != null) {
+            return entry;
         }
+        return mActiveNotifications.get(key);
     }
 
     private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) {
@@ -880,11 +884,19 @@
 
     /** Resorts / filters the current notification set with the current RankingMap */
     public void reapplyFilterAndSort(String reason) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
+            return;
+        }
         updateRankingAndSort(mRanker.getRankingMap(), reason);
     }
 
     /** Calls to NotificationRankingManager and updates mSortedAndFiltered */
     private void updateRankingAndSort(@NonNull RankingMap rankingMap, String reason) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
+            return;
+        }
         mSortedAndFiltered.clear();
         mSortedAndFiltered.addAll(mRanker.updateRanking(
                 rankingMap, mActiveNotifications.values(), reason));
@@ -892,7 +904,7 @@
 
     /** dump the current active notification list. Called from StatusBar */
     public void dump(PrintWriter pw, String indent) {
-        pw.println("NotificationEntryManager");
+        pw.println("NotificationEntryManager (Legacy)");
         int filteredLen = mSortedAndFiltered.size();
         pw.print(indent);
         pw.println("active notifications: " + filteredLen);
@@ -946,6 +958,12 @@
         return mReadOnlyAllNotifications;
     }
 
+    @Nullable
+    @Override
+    public NotificationEntry getEntry(String key) {
+        return getPendingOrActiveNotif(key);
+    }
+
     /** @return A count of the active notifications */
     public int getActiveNotificationsCount() {
         return mReadOnlyNotifications.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
index 4382ab5..2397005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotificationLog
 import javax.inject.Inject
 
@@ -95,6 +96,15 @@
             "FILTER AND SORT reason=$str1"
         })
     }
+
+    fun logUseWhileNewPipelineActive(method: String, reason: String) {
+        buffer.log(TAG, WARNING, {
+            str1 = method
+            str2 = reason
+        }, {
+            "While running New Pipeline: $str1(reason=$str2)"
+        })
+    }
 }
 
 private const val TAG = "NotificationEntryMgr"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
similarity index 61%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index 27ba4c2..50d7324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -13,34 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.systemui.statusbar.notification.collection
 
-package com.android.systemui.statusbar.notification.collection;
-
-import android.os.Handler;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-
-import java.util.Collection;
-import java.util.List;
-
-import javax.inject.Inject;
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import javax.inject.Inject
 
 /**
  * The system that constructs the "shade list", the filtered, grouped, and sorted list of
@@ -52,42 +43,33 @@
  * This list differs from the canonical one we receive from system server in a few ways:
  * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
  *   views haven't been inflated yet. We also filter out some notifications if we're on the lock
- *   screen and notifications for other users. So participate, see
- *   {@link #addPreGroupFilter} and similar methods.
+ *   screen and notifications for other users. To participate, see
+ *   [.addPreGroupFilter] and similar methods.
  * - Grouped: Notifications that are part of the same group are clustered together into a single
  *   GroupEntry. These groups are then transformed in order to remove children or completely split
- *   them apart. To participate, see {@link #addPromoter}.
+ *   them apart. To participate, see [.addPromoter].
  * - Sorted: All top-level notifications are sorted. To participate, see
- *   {@link #setSections} and {@link #setComparators}
+ *   [.setSections] and [.setComparators]
  *
  * The exact order of all hooks is as follows:
- *  0. Collection listeners are fired ({@link #addCollectionListener}).
- *  1. Pre-group filters are fired on each notification ({@link #addPreGroupFilter}).
+ *  0. Collection listeners are fired ([.addCollectionListener]).
+ *  1. Pre-group filters are fired on each notification ([.addPreGroupFilter]).
  *  2. Initial grouping is performed (NotificationEntries will have their parents set
  *     appropriately).
- *  3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
- *  4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
- *  5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
- *  6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
- *  7. Top-level entries within the same section are sorted by NotifComparators
- *     ({@link #setComparators})
- *  8. Finalize filters are fired on each notification ({@link #addFinalizeFilter})
- *  9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener})
- *  9. The list is handed off to the view layer to be rendered
+ *  3. OnBeforeTransformGroupListeners are fired ([.addOnBeforeTransformGroupsListener])
+ *  4. NotifPromoters are called on each notification with a parent ([.addPromoter])
+ *  5. Finalize filters are fired on each notification ([.addFinalizeFilter])
+ *  6. OnBeforeSortListeners are fired ([.addOnBeforeSortListener])
+ *  7. Top-level entries are assigned sections by NotifSections ([.setSections])
+ *  8. Top-level entries within the same section are sorted by NotifComparators ([.setComparators])
+ *  9. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener])
+ *  10. The list is handed off to the view layer to be rendered
  */
 @SysUISingleton
-public class NotifPipeline implements CommonNotifCollection {
-    private final NotifCollection mNotifCollection;
-    private final ShadeListBuilder mShadeListBuilder;
-
-    @Inject
-    public NotifPipeline(
-            NotifCollection notifCollection,
-            ShadeListBuilder shadeListBuilder) {
-        mNotifCollection = notifCollection;
-        mShadeListBuilder = shadeListBuilder;
-    }
-
+class NotifPipeline @Inject constructor(
+    private val mNotifCollection: NotifCollection,
+    private val mShadeListBuilder: ShadeListBuilder
+) : CommonNotifCollection {
     /**
      * Returns the list of all known notifications, i.e. the notifications that are currently posted
      * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
@@ -95,38 +77,35 @@
      *
      * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
      */
-    @Override
-    public Collection<NotificationEntry> getAllNotifs() {
-        return mNotifCollection.getAllNotifs();
+    override fun getAllNotifs(): Collection<NotificationEntry> {
+        return mNotifCollection.allNotifs
     }
 
-    @Override
-    public void addCollectionListener(NotifCollectionListener listener) {
-        mNotifCollection.addCollectionListener(listener);
+    override fun addCollectionListener(listener: NotifCollectionListener) {
+        mNotifCollection.addCollectionListener(listener)
     }
 
     /**
      * Returns the NotificationEntry associated with [key].
      */
-    @Nullable
-    public NotificationEntry getEntry(String key) {
-        return mNotifCollection.getEntry(key);
+    override fun getEntry(key: String): NotificationEntry? {
+        return mNotifCollection.getEntry(key)
     }
 
     /**
      * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
      * dismissed or retracted by system server to be temporarily retained in the collection.
      */
-    public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
-        mNotifCollection.addNotificationLifetimeExtender(extender);
+    fun addNotificationLifetimeExtender(extender: NotifLifetimeExtender) {
+        mNotifCollection.addNotificationLifetimeExtender(extender)
     }
 
     /**
      * Registers a dismiss interceptor. Dismiss interceptors can cause notifications that have been
      * dismissed by the user to be retained (won't send a dismissal to system server).
      */
-    public void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) {
-        mNotifCollection.addNotificationDismissInterceptor(interceptor);
+    fun addNotificationDismissInterceptor(interceptor: NotifDismissInterceptor) {
+        mNotifCollection.addNotificationDismissInterceptor(interceptor)
     }
 
     /**
@@ -135,16 +114,16 @@
      * returns true, the notification is removed from the pipeline (and no other filters are
      * called on that notif).
      */
-    public void addPreGroupFilter(NotifFilter filter) {
-        mShadeListBuilder.addPreGroupFilter(filter);
+    fun addPreGroupFilter(filter: NotifFilter) {
+        mShadeListBuilder.addPreGroupFilter(filter)
     }
 
     /**
      * Called after notifications have been filtered and after the initial grouping has been
      * performed but before NotifPromoters have had a chance to promote children out of groups.
      */
-    public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
-        mShadeListBuilder.addOnBeforeTransformGroupsListener(listener);
+    fun addOnBeforeTransformGroupsListener(listener: OnBeforeTransformGroupsListener) {
+        mShadeListBuilder.addOnBeforeTransformGroupsListener(listener)
     }
 
     /**
@@ -154,34 +133,34 @@
      * registered. If any promoter returns true, the notification is removed from the group (and no
      * other promoters are called on it).
      */
-    public void addPromoter(NotifPromoter promoter) {
-        mShadeListBuilder.addPromoter(promoter);
+    fun addPromoter(promoter: NotifPromoter) {
+        mShadeListBuilder.addPromoter(promoter)
     }
 
     /**
      * Called after notifs have been filtered and groups have been determined but before sections
      * have been determined or the notifs have been sorted.
      */
-    public void addOnBeforeSortListener(OnBeforeSortListener listener) {
-        mShadeListBuilder.addOnBeforeSortListener(listener);
+    fun addOnBeforeSortListener(listener: OnBeforeSortListener) {
+        mShadeListBuilder.addOnBeforeSortListener(listener)
     }
 
     /**
      * Sections that are used to sort top-level entries.  If two entries have the same section,
      * NotifComparators are consulted. Sections from this list are called in order for each
      * notification passed through the pipeline. The first NotifSection to return true for
-     * {@link NotifSectioner#isInSection(ListEntry)} sets the entry as part of its Section.
+     * [NotifSectioner.isInSection] sets the entry as part of its Section.
      */
-    public void setSections(List<NotifSectioner> sections) {
-        mShadeListBuilder.setSectioners(sections);
+    fun setSections(sections: List<NotifSectioner>) {
+        mShadeListBuilder.setSectioners(sections)
     }
 
     /**
      * StabilityManager that is used to determine whether to suppress group and section changes.
      * This should only be set once.
      */
-    public void setVisualStabilityManager(NotifStabilityManager notifStabilityManager) {
-        mShadeListBuilder.setNotifStabilityManager(notifStabilityManager);
+    fun setVisualStabilityManager(notifStabilityManager: NotifStabilityManager) {
+        mShadeListBuilder.setNotifStabilityManager(notifStabilityManager)
     }
 
     /**
@@ -189,16 +168,16 @@
      * comparators are executed in order until one of them returns a non-zero result. If all return
      * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
      */
-    public void setComparators(List<NotifComparator> comparators) {
-        mShadeListBuilder.setComparators(comparators);
+    fun setComparators(comparators: List<NotifComparator>) {
+        mShadeListBuilder.setComparators(comparators)
     }
 
     /**
      * Called after notifs have been filtered once, grouped, and sorted but before the final
      * filtering.
      */
-    public void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) {
-        mShadeListBuilder.addOnBeforeFinalizeFilterListener(listener);
+    fun addOnBeforeFinalizeFilterListener(listener: OnBeforeFinalizeFilterListener) {
+        mShadeListBuilder.addOnBeforeFinalizeFilterListener(listener)
     }
 
     /**
@@ -208,21 +187,21 @@
      * true, the notification is removed from the pipeline (and no other filters are called on that
      * notif).
      */
-    public void addFinalizeFilter(NotifFilter filter) {
-        mShadeListBuilder.addFinalizeFilter(filter);
+    fun addFinalizeFilter(filter: NotifFilter) {
+        mShadeListBuilder.addFinalizeFilter(filter)
     }
 
     /**
      * Called at the end of the pipeline after the notif list has been finalized but before it has
      * been handed off to the view layer.
      */
-    public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
-        mShadeListBuilder.addOnBeforeRenderListListener(listener);
+    fun addOnBeforeRenderListListener(listener: OnBeforeRenderListListener) {
+        mShadeListBuilder.addOnBeforeRenderListListener(listener)
     }
 
     /** Registers an invalidator that can be used to invalidate the entire notif list. */
-    public void addPreRenderInvalidator(Invalidator invalidator) {
-        mShadeListBuilder.addPreRenderInvalidator(invalidator);
+    fun addPreRenderInvalidator(invalidator: Invalidator) {
+        mShadeListBuilder.addPreRenderInvalidator(invalidator)
     }
 
     /**
@@ -232,8 +211,8 @@
      * @param name the name of the component that will update notifiations
      * @return an updater
      */
-    public InternalNotifUpdater getInternalNotifUpdater(String name) {
-        return mNotifCollection.getInternalNotifUpdater(name);
+    fun getInternalNotifUpdater(name: String?): InternalNotifUpdater {
+        return mNotifCollection.getInternalNotifUpdater(name)
     }
 
     /**
@@ -241,8 +220,20 @@
      * are currently present in the shade. If this method is called during pipeline execution it
      * will return the current state of the list, which will likely be only partially-generated.
      */
-    public List<ListEntry> getShadeList() {
-        return mShadeListBuilder.getShadeList();
+    val shadeList: List<ListEntry>
+        get() = mShadeListBuilder.shadeList
+
+    /**
+     * Constructs a flattened representation of the notification tree, where each group will have
+     * the summary (if present) followed by the children.
+     */
+    fun getFlatShadeList(): List<NotificationEntry> = shadeList.flatMap { entry ->
+        when (entry) {
+            is NotificationEntry -> sequenceOf(entry)
+            is GroupEntry -> (entry.summary?.let { sequenceOf(it) }.orEmpty() +
+                    entry.children)
+            else -> throw RuntimeException("Unexpected entry $entry")
+        }
     }
 
     /**
@@ -251,20 +242,9 @@
      * will return the number of notifications in its current state, which will likely be only
      * partially-generated.
      */
-    public int getShadeListCount() {
-        final List<ListEntry> entries = getShadeList();
-        int numNotifs = 0;
-        for (int i = 0; i < entries.size(); i++) {
-            final ListEntry entry = entries.get(i);
-            if (entry instanceof GroupEntry) {
-                final GroupEntry parentEntry = (GroupEntry) entry;
-                numNotifs++; // include the summary in the count
-                numNotifs += parentEntry.getChildren().size();
-            } else {
-                numNotifs++;
-            }
-        }
-
-        return numNotifs;
+    fun getShadeListCount(): Int = shadeList.sumOf { entry ->
+        // include the summary in the count
+        if (entry is GroupEntry) 1 + entry.children.size
+        else 1
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 15f0d88..d013261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -134,7 +134,7 @@
                 mInterceptedDismissalEntries.remove(entry.getKey());
                 mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
                         dismissedByUserStats);
-            } else if (mNotifPipeline.getAllNotifs().contains(entry)) {
+            } else if (mNotifPipeline.getEntry(entry.getKey()) != null) {
                 // Bubbles are hiding the notifications from the shade, but the bubble was
                 // deleted; therefore, the notification should be cancelled as if it were a user
                 // dismissal (this won't re-enter handleInterceptDimissal because Bubbles
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index 23417fe..66ec30d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -23,15 +23,13 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -40,7 +38,7 @@
  * information about the interaction to the notification pipeline.
  */
 public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback {
-    private final NotifPipeline mNotifPipeline;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotifCollection mNotifCollection;
     private final HeadsUpManager mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
@@ -48,14 +46,14 @@
     private final GroupMembershipManager mGroupMembershipManager;
 
     public OnUserInteractionCallbackImpl(
-            NotifPipeline notifPipeline,
+            NotificationVisibilityProvider visibilityProvider,
             NotifCollection notifCollection,
             HeadsUpManager headsUpManager,
             StatusBarStateController statusBarStateController,
             VisualStabilityCoordinator visualStabilityCoordinator,
             GroupMembershipManager groupMembershipManager
     ) {
-        mNotifPipeline = notifPipeline;
+        mVisibilityProvider = visibilityProvider;
         mNotifCollection = notifCollection;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
@@ -91,12 +89,7 @@
                 new DismissedByUserStats(
                     dismissalSurface,
                     DISMISS_SENTIMENT_NEUTRAL,
-                    NotificationVisibility.obtain(
-                            entry.getKey(),
-                            entry.getRanking().getRank(),
-                            mNotifPipeline.getShadeListCount(),
-                            true,
-                            NotificationLogger.getNotificationLocation(entry)))
+                    mVisibilityProvider.obtain(entry, true))
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt
new file mode 100644
index 0000000..5c70f32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.notification.collection.legacy
+
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.statusbar.notification.NotificationEntryManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import javax.inject.Inject
+
+/** Legacy pipeline implementation for getting [NotificationVisibility]. */
+class LegacyNotificationVisibilityProvider @Inject constructor(
+    private val notifEntryManager: NotificationEntryManager
+) : NotificationVisibilityProvider {
+    override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility {
+        val count: Int = notifEntryManager.activeNotificationsCount
+        val rank = entry.ranking.rank
+        val hasRow = entry.row != null
+        val location = NotificationLogger.getNotificationLocation(entry)
+        return NotificationVisibility.obtain(entry.key, rank, count, visible && hasRow, location)
+    }
+
+    override fun obtain(key: String, visible: Boolean): NotificationVisibility {
+        val entry: NotificationEntry? = notifEntryManager.getActiveNotificationUnfiltered(key)
+        val count: Int = notifEntryManager.activeNotificationsCount
+        val rank = entry?.ranking?.rank ?: -1
+        val hasRow = entry?.row != null
+        val location = NotificationLogger.getNotificationLocation(entry)
+        return NotificationVisibility.obtain(key, rank, count, visible && hasRow, location)
+    }
+
+    override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
+            NotificationLogger.getNotificationLocation(
+                    notifEntryManager.getActiveNotificationUnfiltered(key))
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
index 11f22dd..3b114bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
@@ -22,13 +22,12 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -37,6 +36,7 @@
  */
 public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCallback {
     private final NotificationEntryManager mNotificationEntryManager;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final HeadsUpManager mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
     private final VisualStabilityManager mVisualStabilityManager;
@@ -44,12 +44,14 @@
 
     public OnUserInteractionCallbackImplLegacy(
             NotificationEntryManager notificationEntryManager,
+            NotificationVisibilityProvider visibilityProvider,
             HeadsUpManager headsUpManager,
             StatusBarStateController statusBarStateController,
             VisualStabilityManager visualStabilityManager,
             GroupMembershipManager groupMembershipManager
     ) {
         mNotificationEntryManager = notificationEntryManager;
+        mVisibilityProvider = visibilityProvider;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
         mVisualStabilityManager = visualStabilityManager;
@@ -88,12 +90,7 @@
                 new DismissedByUserStats(
                         dismissalSurface,
                         DISMISS_SENTIMENT_NEUTRAL,
-                        NotificationVisibility.obtain(
-                                entry.getKey(),
-                                entry.getRanking().getRank(),
-                                mNotificationEntryManager.getActiveNotificationsCount(),
-                                true,
-                                NotificationLogger.getNotificationLocation(entry))),
+                        mVisibilityProvider.obtain(entry, true)),
                 cancellationReason
         );
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
index b4c2bb8..471c357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.notifcollection;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -45,4 +47,10 @@
      * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
      */
     Collection<NotificationEntry> getAllNotifs();
+
+    /**
+     * Returns the notification entry for the given notification key;
+     * the returned entry (if present) may be in any state.
+     */
+    @Nullable NotificationEntry getEntry(String key);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt
new file mode 100644
index 0000000..c492d14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.notification.collection.render
+
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * An interface for getting the current [NotificationVisibility] object for a notification.
+ */
+interface NotificationVisibilityProvider {
+    /** Given a notification entry, return the visibility object */
+    fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility
+    /** Given a notification key, return the visibility object */
+    fun obtain(key: String, visible: Boolean): NotificationVisibility
+    /** Given a notification key, return the location */
+    fun getLocation(key: String): NotificationVisibility.NotificationLocation
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt
new file mode 100644
index 0000000..51de08d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 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.notification.collection.render
+
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import javax.inject.Inject
+
+/** New pipeline implementation for getting [NotificationVisibility]. */
+class NotificationVisibilityProviderImpl @Inject constructor(
+    private val notifPipeline: NotifPipeline
+) : NotificationVisibilityProvider {
+    override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility {
+        val count: Int = notifPipeline.getShadeListCount()
+        val rank = entry.ranking.rank
+        val hasRow = entry.row != null
+        val location = NotificationLogger.getNotificationLocation(entry)
+        return NotificationVisibility.obtain(entry.key, rank, count, visible && hasRow, location)
+    }
+
+    override fun obtain(key: String, visible: Boolean): NotificationVisibility =
+        notifPipeline.getEntry(key)?.let { return obtain(it, visible) }
+            ?: NotificationVisibility.obtain(key, -1, notifPipeline.getShadeListCount(), false)
+
+    override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
+            NotificationLogger.getNotificationLocation(notifPipeline.getEntry(key))
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 1eb007e..a19549c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
 import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions;
+import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -63,6 +64,8 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProviderImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -208,14 +211,20 @@
     static NotificationLogger provideNotificationLogger(
             NotificationListener notificationListener,
             @UiBackground Executor uiBgExecutor,
+            FeatureFlags featureFlags,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager entryManager,
+            NotifPipeline notifPipeline,
             StatusBarStateController statusBarStateController,
             NotificationLogger.ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
         return new NotificationLogger(
                 notificationListener,
                 uiBgExecutor,
+                featureFlags,
+                visibilityProvider,
                 entryManager,
+                notifPipeline,
                 statusBarStateController,
                 expansionStateLogger,
                 notificationPanelLogger);
@@ -278,6 +287,20 @@
     }
 
     /**
+     * Provide the object which can be used to obtain NotificationVisibility objects.
+     */
+    @Provides
+    @SysUISingleton
+    static NotificationVisibilityProvider provideNotificationVisibilityProvider(
+            FeatureFlags featureFlags,
+            Lazy<NotificationVisibilityProviderImpl> newProvider,
+            Lazy<LegacyNotificationVisibilityProvider> legacyProvider) {
+        return featureFlags.isNewNotifPipelineRenderingEnabled()
+                ? newProvider.get()
+                : legacyProvider.get();
+    }
+
+    /**
      * Provide the active implementation for presenting notifications.
      */
     @Provides
@@ -301,15 +324,15 @@
             FeatureFlags featureFlags,
             HeadsUpManager headsUpManager,
             StatusBarStateController statusBarStateController,
-            Lazy<NotifPipeline> pipeline,
             Lazy<NotifCollection> notifCollection,
+            Lazy<NotificationVisibilityProvider> visibilityProvider,
             Lazy<VisualStabilityCoordinator> visualStabilityCoordinator,
             NotificationEntryManager entryManager,
             VisualStabilityManager visualStabilityManager,
             Lazy<GroupMembershipManager> groupMembershipManagerLazy) {
         return featureFlags.isNewNotifPipelineRenderingEnabled()
                 ? new OnUserInteractionCallbackImpl(
-                        pipeline.get(),
+                        visibilityProvider.get(),
                         notifCollection.get(),
                         headsUpManager,
                         statusBarStateController,
@@ -317,6 +340,7 @@
                         groupMembershipManagerLazy.get())
                 : new OnUserInteractionCallbackImplLegacy(
                         entryManager,
+                        visibilityProvider.get(),
                         headsUpManager,
                         statusBarStateController,
                         visualStabilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 4441270..993e38d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -26,6 +26,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.GuardedBy;
@@ -33,13 +34,17 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -70,7 +75,10 @@
     // Dependencies:
     private final NotificationListenerService mNotificationListener;
     private final Executor mUiBgExecutor;
+    private final FeatureFlags mFeatureFlags;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationEntryManager mEntryManager;
+    private final NotifPipeline mNotifPipeline;
     private final NotificationPanelLogger mNotificationPanelLogger;
     private final ExpansionStateLogger mExpansionStateLogger;
 
@@ -127,7 +135,7 @@
             //    notifications.
             // 3. Report newly visible and no-longer visible notifications.
             // 4. Keep currently visible notifications for next report.
-            List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
+            List<NotificationEntry> activeNotifications = getVisibleNotifications();
             int N = activeNotifications.size();
             for (int i = 0; i < N; i++) {
                 NotificationEntry entry = activeNotifications.get(i);
@@ -166,6 +174,14 @@
         }
     };
 
+    private List<NotificationEntry> getVisibleNotifications() {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return mNotifPipeline.getFlatShadeList();
+        } else {
+            return mEntryManager.getVisibleNotifications();
+        }
+    }
+
     /**
      * Returns the location of the notification referenced by the given {@link NotificationEntry}.
      */
@@ -202,13 +218,19 @@
      */
     public NotificationLogger(NotificationListener notificationListener,
             @UiBackground Executor uiBgExecutor,
+            FeatureFlags featureFlags,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager entryManager,
+            NotifPipeline notifPipeline,
             StatusBarStateController statusBarStateController,
             ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
         mNotificationListener = notificationListener;
         mUiBgExecutor = uiBgExecutor;
+        mFeatureFlags = featureFlags;
+        mVisibilityProvider = visibilityProvider;
         mEntryManager = entryManager;
+        mNotifPipeline = notifPipeline;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mExpansionStateLogger = expansionStateLogger;
@@ -216,7 +238,15 @@
         // Not expected to be destroyed, don't need to unsubscribe
         statusBarStateController.addCallback(this);
 
-        entryManager.addNotificationEntryListener(new NotificationEntryListener() {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            registerNewPipelineListener();
+        } else {
+            registerLegacyListener();
+        }
+    }
+
+    private void registerLegacyListener() {
+        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
             public void onEntryRemoved(
                     NotificationEntry entry,
@@ -240,6 +270,20 @@
         });
     }
 
+    private void registerNewPipelineListener() {
+        mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+            @Override
+            public void onEntryUpdated(@NonNull NotificationEntry entry, boolean fromSystem) {
+                mExpansionStateLogger.onEntryUpdated(entry.getKey());
+            }
+
+            @Override
+            public void onEntryRemoved(@NonNull NotificationEntry entry, int reason) {
+                mExpansionStateLogger.onEntryRemoved(entry.getKey());
+            }
+        });
+    }
+
     public void setUpWithContainer(NotificationListContainer listContainer) {
         mListContainer = listContainer;
     }
@@ -407,8 +451,7 @@
         // Once we know panelExpanded and Dozing, turn logging on & off when appropriate
         boolean lockscreen = mLockscreen == null ? false : mLockscreen;
         if (mPanelExpanded && !mDozing) {
-            mNotificationPanelLogger.logPanelShown(lockscreen,
-                    mEntryManager.getVisibleNotifications());
+            mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
             if (DEBUG) {
                 Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen);
             }
@@ -440,8 +483,7 @@
      * Called when the notification is expanded / collapsed.
      */
     public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
-        NotificationVisibility.NotificationLocation location =
-                getNotificationLocation(mEntryManager.getActiveNotificationUnfiltered(key));
+        NotificationVisibility.NotificationLocation location = mVisibilityProvider.getLocation(key);
         mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 8b8b64d..9492996 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -58,7 +58,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
@@ -99,6 +98,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -145,6 +145,7 @@
 
     private final boolean mAllowLongPress;
     private final NotificationGutsManager mNotificationGutsManager;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
     private final TunerService mTunerService;
@@ -618,6 +619,7 @@
     public NotificationStackScrollLayoutController(
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             NotificationGutsManager notificationGutsManager,
+            NotificationVisibilityProvider visibilityProvider,
             HeadsUpManagerPhone headsUpManager,
             NotificationRoundnessManager notificationRoundnessManager,
             TunerService tunerService,
@@ -655,6 +657,7 @@
             ShadeController shadeController) {
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
+        mVisibilityProvider = visibilityProvider;
         mHeadsUpManager = headsUpManager;
         mNotificationRoundnessManager = notificationRoundnessManager;
         mTunerService = tunerService;
@@ -1384,19 +1387,11 @@
         mView.resetCheckSnoozeLeavebehind();
     }
 
-    private DismissedByUserStats getDismissedByUserStats(
-            NotificationEntry entry,
-            int numVisibleEntries
-    ) {
+    private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
         return new DismissedByUserStats(
                 DISMISSAL_SHADE,
                 DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(
-                        entry.getKey(),
-                        entry.getRanking().getRank(),
-                        numVisibleEntries,
-                        true,
-                        NotificationLogger.getNotificationLocation(entry)));
+                mVisibilityProvider.obtain(entry, true));
     }
 
     /**
@@ -1447,13 +1442,10 @@
             } else {
                 final List<Pair<NotificationEntry, DismissedByUserStats>>
                         entriesWithRowsDismissedFromShade = new ArrayList<>();
-                final int numVisibleEntries = mNotifPipeline.getShadeListCount();
                 for (ExpandableNotificationRow row : viewsToRemove) {
                     final NotificationEntry entry = row.getEntry();
                     entriesWithRowsDismissedFromShade.add(
-                            new Pair<>(
-                                    entry,
-                                    getDismissedByUserStats(entry, numVisibleEntries)));
+                            new Pair<>(entry, getDismissedByUserStats(entry)));
                 }
                 mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
             }
@@ -1462,9 +1454,7 @@
                 if (canChildBeDismissed(rowToRemove)) {
                     mNotificationEntryManager.performRemoveNotification(
                             rowToRemove.getEntry().getSbn(),
-                            getDismissedByUserStats(
-                                    rowToRemove.getEntry(),
-                                    mNotificationEntryManager.getActiveNotificationsCount()),
+                            getDismissedByUserStats(rowToRemove.getEntry()),
                             NotificationListenerService.REASON_CANCEL_ALL);
                 } else {
                     rowToRemove.resetTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 2098a1d..2b62e0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -26,6 +26,7 @@
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
 
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Fragment;
 import android.os.Bundle;
@@ -46,8 +47,9 @@
 import com.android.systemui.statusbar.OperatorNameView;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
@@ -104,12 +106,13 @@
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final PanelExpansionStateManager mPanelExpansionStateManager;
     private final StatusBarIconController mStatusBarIconController;
+    private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
 
     private List<String> mBlockedIcons = new ArrayList<>();
 
     private SignalCallback mSignalCallback = new SignalCallback() {
         @Override
-        public void setIsAirplaneMode(NetworkController.IconState icon) {
+        public void setIsAirplaneMode(@NonNull IconState icon) {
             mCommandQueue.recomputeDisableFlags(getContext().getDisplayId(), true /* animate */);
         }
     };
@@ -131,6 +134,7 @@
             PanelExpansionStateManager panelExpansionStateManager,
             FeatureFlags featureFlags,
             StatusBarIconController statusBarIconController,
+            StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
             NetworkController networkController,
             StatusBarStateController statusBarStateController,
@@ -146,6 +150,7 @@
         mPanelExpansionStateManager = panelExpansionStateManager;
         mFeatureFlags = featureFlags;
         mStatusBarIconController = statusBarIconController;
+        mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mKeyguardStateController = keyguardStateController;
         mNetworkController = networkController;
         mStatusBarStateController = statusBarStateController;
@@ -372,10 +377,7 @@
                         StatusBar::hideStatusBarIconsWhenExpanded).orElse(false)) {
             return true;
         }
-        if (statusBarOptional.map(StatusBar::hideStatusBarIconsForBouncer).orElse(false)) {
-            return true;
-        }
-        return false;
+        return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
     }
 
     private void hideSystemIconArea(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index f90bfe4..e26e69f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -3938,45 +3938,6 @@
             private long mLastTouchDownTime = -1L;
 
             @Override
-            public boolean onTouchForwardedFromStatusBar(MotionEvent event) {
-                // TODO(b/202981994): Move the touch debugging in this method to a central location.
-                //  (Right now, it's split between StatusBar and here.)
-
-                // If panels aren't enabled, ignore the gesture and don't pass it down to the
-                // panel view.
-                if (!mCommandQueue.panelsEnabled()) {
-                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
-                        Log.v(
-                                TAG,
-                                String.format(
-                                        "onTouchForwardedFromStatusBar: "
-                                                + "panel disabled, ignoring touch at (%d,%d)",
-                                        (int) event.getX(),
-                                        (int) event.getY()
-                                )
-                        );
-                    }
-                    return false;
-                }
-
-                // If the view that would receive the touch is disabled, just have status bar eat
-                // the gesture.
-                if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
-                    Log.v(TAG,
-                            String.format(
-                                    "onTouchForwardedFromStatusBar: "
-                                            + "panel view disabled, eating touch at (%d,%d)",
-                                    (int) event.getX(),
-                                    (int) event.getY()
-                            )
-                    );
-                    return true;
-                }
-
-                return mView.dispatchTouchEvent(event);
-            }
-
-            @Override
             public boolean onInterceptTouchEvent(MotionEvent event) {
                 if (mBlockTouches || mQs.disallowPanelTouches()) {
                     return false;
@@ -4087,6 +4048,55 @@
         };
     }
 
+    private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
+            new PhoneStatusBarView.TouchEventHandler() {
+                @Override
+                public void onInterceptTouchEvent(MotionEvent event) {
+                    mStatusBar.onTouchEvent(event);
+                }
+
+                @Override
+                public boolean handleTouchEvent(MotionEvent event) {
+                    mStatusBar.onTouchEvent(event);
+
+                    // TODO(b/202981994): Move the touch debugging in this method to a central
+                    //  location. (Right now, it's split between StatusBar and here.)
+
+                    // If panels aren't enabled, ignore the gesture and don't pass it down to the
+                    // panel view.
+                    if (!mCommandQueue.panelsEnabled()) {
+                        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                            Log.v(
+                                    TAG,
+                                    String.format(
+                                            "onTouchForwardedFromStatusBar: "
+                                                    + "panel disabled, ignoring touch at (%d,%d)",
+                                            (int) event.getX(),
+                                            (int) event.getY()
+                                    )
+                            );
+                        }
+                        return false;
+                    }
+
+                    // If the view that would receive the touch is disabled, just have status bar
+                    // eat the gesture.
+                    if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
+                        Log.v(TAG,
+                                String.format(
+                                        "onTouchForwardedFromStatusBar: "
+                                                + "panel view disabled, eating touch at (%d,%d)",
+                                        (int) event.getX(),
+                                        (int) event.getY()
+                                )
+                        );
+                        return true;
+                    }
+
+                    return mView.dispatchTouchEvent(event);
+                }
+            };
+
     @Override
     protected PanelViewController.OnConfigurationChangedListener
             createOnConfigurationChangedListener() {
@@ -4880,6 +4890,6 @@
 
     /** Returns the handler that the status bar should forward touches to. */
     public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
-        return getTouchHandler()::onTouchForwardedFromStatusBar;
+        return mStatusBarViewTouchEventHandler;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 38cf787..2823d98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -292,10 +292,6 @@
                 : mTouchSlop;
     }
 
-    protected TouchHandler getTouchHandler() {
-        return mTouchHandler;
-    }
-
     private void addMovement(MotionEvent event) {
         // Add movement to velocity tracker using raw screen X and Y coordinates instead
         // of window coordinates because the window frame may be moving at the same time.
@@ -1161,17 +1157,7 @@
         return new OnConfigurationChangedListener();
     }
 
-    public abstract class TouchHandler implements View.OnTouchListener {
-        /**
-         * Method called when a touch has occurred on {@link PhoneStatusBarView}.
-         *
-         * Touches that occur on the status bar view may have ramifications for the notification
-         * panel (e.g. a touch that pulls down the shade could start on the status bar), so we need
-         * to notify the panel controller when these touches occur.
-         *
-         * Returns true if the event was handled and false otherwise.
-         */
-        public abstract boolean onTouchForwardedFromStatusBar(MotionEvent event);
+    public class TouchHandler implements View.OnTouchListener {
 
         public boolean onInterceptTouchEvent(MotionEvent event) {
             if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 0acf2ac..f67d181 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -47,9 +47,6 @@
     private static final String TAG = "PhoneStatusBarView";
     private final StatusBarContentInsetsProvider mContentInsetsProvider;
 
-    StatusBar mBar;
-
-    private ScrimController mScrimController;
     private DarkReceiver mBattery;
     private DarkReceiver mClock;
     private int mRotationOrientation = -1;
@@ -73,18 +70,10 @@
         mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
     }
 
-    public void setBar(StatusBar bar) {
-        mBar = bar;
-    }
-
     void setTouchEventHandler(TouchEventHandler handler) {
         mTouchEventHandler = handler;
     }
 
-    public void setScrimController(ScrimController scrimController) {
-        mScrimController = scrimController;
-    }
-
     @Override
     public void onFinishInflate() {
         mBattery = findViewById(R.id.battery);
@@ -171,7 +160,6 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        mBar.onTouchEvent(event);
         if (mTouchEventHandler == null) {
             Log.w(
                     TAG,
@@ -188,7 +176,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        mBar.onTouchEvent(event);
+        mTouchEventHandler.onInterceptTouchEvent(event);
         return super.onInterceptTouchEvent(event);
     }
 
@@ -269,13 +257,26 @@
     }
 
     /**
-     * A handler repsonsible for all touch event handling on the status bar.
+     * A handler responsible for all touch event handling on the status bar.
      *
-     * The handler will be notified each time {@link this#onTouchEvent} is called, and the return
-     * value from the handler will be returned from {@link this#onTouchEvent}.
+     * Touches that occur on the status bar view may have ramifications for the notification
+     * panel (e.g. a touch that pulls down the shade could start on the status bar), so this
+     * interface provides a way to notify the panel controller when these touches occur.
+     *
+     * The handler will be notified each time {@link PhoneStatusBarView#onTouchEvent} and
+     * {@link PhoneStatusBarView#onInterceptTouchEvent} are called.
      **/
     public interface TouchEventHandler {
-        /** Called each time {@link this#onTouchEvent} is called. */
+        /** Called each time {@link PhoneStatusBarView#onInterceptTouchEvent} is called. */
+        void onInterceptTouchEvent(MotionEvent event);
+
+        /**
+         * Called each time {@link PhoneStatusBarView#onTouchEvent} is called.
+         *
+         * Should return true if the touch was handled by this handler and false otherwise. The
+         * return value from the handler will be returned from
+         * {@link PhoneStatusBarView#onTouchEvent}.
+         */
         boolean handleTouchEvent(MotionEvent event);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 28c6b99..afa333a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -339,6 +339,12 @@
     void setWindowState(int state) {
         mStatusBarWindowState =  state;
         mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
+        mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden);
+        if (getStatusBarView() != null) {
+            // Should #updateHideIconsForBouncer always be called, regardless of whether we have a
+            //   status bar view? If so, we can make #updateHideIconsForBouncer private.
+            mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false);
+        }
     }
 
     void acquireGestureWakeLock(long time) {
@@ -358,14 +364,6 @@
         return mStatusBarMode;
     }
 
-    boolean getWereIconsJustHidden() {
-        return mWereIconsJustHidden;
-    }
-
-    void setWereIconsJustHidden(boolean justHidden) {
-        mWereIconsJustHidden = justHidden;
-    }
-
     void resendMessage(int msg) {
         mMessageRouter.cancelMessages(msg);
         mMessageRouter.sendMessage(msg);
@@ -508,6 +506,7 @@
     private final SystemStatusAnimationScheduler mAnimationScheduler;
     private final StatusBarLocationPublisher mStatusBarLocationPublisher;
     private final StatusBarIconController mStatusBarIconController;
+    private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
 
     // expanded notifications
     // the sliding/resizing panel within the notification window
@@ -640,10 +639,7 @@
     private int mLastLoggedStateFingerprint;
     private boolean mTopHidesStatusBar;
     private boolean mStatusBarWindowHidden;
-    private boolean mHideIconsForBouncer;
     private boolean mIsOccluded;
-    private boolean mWereIconsJustHidden;
-    private boolean mBouncerWasShowingWhenHidden;
     private boolean mIsLaunchingActivityOverLockscreen;
 
     private final UserSwitcherController mUserSwitcherController;
@@ -776,6 +772,7 @@
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
             StatusBarIconController statusBarIconController,
+            StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             FeatureFlags featureFlags,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -872,6 +869,7 @@
         mAnimationScheduler = animationScheduler;
         mStatusBarLocationPublisher = locationPublisher;
         mStatusBarIconController = statusBarIconController;
+        mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mFeatureFlags = featureFlags;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mMainHandler = mainHandler;
@@ -937,6 +935,7 @@
         mDisplay = mContext.getDisplay();
         mDisplayId = mDisplay.getDisplayId();
         updateDisplaySize();
+        mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
 
         // start old BaseStatusBar.start().
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -1140,8 +1139,6 @@
 
                     PhoneStatusBarView oldStatusBarView = mStatusBarView;
                     mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
-                    mStatusBarView.setBar(this);
-                    mStatusBarView.setScrimController(mScrimController);
 
                     mPhoneStatusBarViewController = mPhoneStatusBarViewControllerFactory
                             .create(mStatusBarView, mNotificationPanelViewController
@@ -1197,6 +1194,7 @@
                                 mPanelExpansionStateManager,
                                 mFeatureFlags,
                                 mStatusBarIconController,
+                                mStatusBarHideIconsForBouncerManager,
                                 mKeyguardStateController,
                                 mNetworkController,
                                 mStatusBarStateController,
@@ -1872,7 +1870,7 @@
             mNotificationLogger.onPanelExpandedChanged(isExpanded);
         }
         mPanelExpanded = isExpanded;
-        updateHideIconsForBouncer(false /* animate */);
+        mStatusBarHideIconsForBouncerManager.setPanelExpandedAndTriggerUpdate(isExpanded);
         mNotificationShadeWindowController.setPanelExpanded(isExpanded);
         mStatusBarStateController.setPanelExpanded(isExpanded);
         if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
@@ -1916,46 +1914,8 @@
 
     public void setOccluded(boolean occluded) {
         mIsOccluded = occluded;
+        mStatusBarHideIconsForBouncerManager.setIsOccludedAndTriggerUpdate(occluded);
         mScrimController.setKeyguardOccluded(occluded);
-        updateHideIconsForBouncer(false /* animate */);
-    }
-
-    public boolean hideStatusBarIconsForBouncer() {
-        return mHideIconsForBouncer || mWereIconsJustHidden;
-    }
-
-    /**
-     * Decides if the status bar (clock + notifications + signal cluster) should be visible
-     * or not when showing the bouncer.
-     *
-     * We want to hide it when:
-     * • User swipes up on the keyguard
-     * • Locked activity that doesn't show a status bar requests the bouncer
-     *
-     * @param animate should the change of the icons be animated.
-     */
-    void updateHideIconsForBouncer(boolean animate) {
-        boolean hideBecauseApp = mTopHidesStatusBar && mIsOccluded
-                && (mStatusBarWindowHidden || mBouncerShowing);
-        boolean hideBecauseKeyguard = !mPanelExpanded && !mIsOccluded && mBouncerShowing;
-        boolean shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard;
-        if (mHideIconsForBouncer != shouldHideIconsForBouncer) {
-            mHideIconsForBouncer = shouldHideIconsForBouncer;
-            if (!shouldHideIconsForBouncer && mBouncerWasShowingWhenHidden) {
-                // We're delaying the showing, since most of the time the fullscreen app will
-                // hide the icons again and we don't want them to fade in and out immediately again.
-                mWereIconsJustHidden = true;
-                mMainExecutor.executeDelayed(() -> {
-                    mWereIconsJustHidden = false;
-                    mCommandQueue.recomputeDisableFlags(mDisplayId, true);
-                }, 500);
-            } else {
-                mCommandQueue.recomputeDisableFlags(mDisplayId, animate);
-            }
-        }
-        if (shouldHideIconsForBouncer) {
-            mBouncerWasShowingWhenHidden = mBouncerShowing;
-        }
     }
 
     public boolean headsUpShouldBeVisible() {
@@ -3500,7 +3460,7 @@
         mKeyguardBypassController.setBouncerShowing(bouncerShowing);
         mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
         setBouncerShowingForStatusBarComponents(bouncerShowing);
-        updateHideIconsForBouncer(true /* animate */);
+        mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
         mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
         updateScrimController();
         if (!mBouncerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index bb1daa2..a77a097 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -95,6 +95,7 @@
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final NotificationShadeWindowView mNotificationShadeWindowView;
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+    private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     private final PowerManager mPowerManager;
     private final VibratorHelper mVibratorHelper;
     private final Optional<Vibrator> mVibratorOptional;
@@ -132,6 +133,7 @@
             SysuiStatusBarStateController statusBarStateController,
             NotificationShadeWindowView notificationShadeWindowView,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+            StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             PowerManager powerManager,
             VibratorHelper vibratorHelper,
             Optional<Vibrator> vibratorOptional,
@@ -158,6 +160,7 @@
         mStatusBarStateController = statusBarStateController;
         mNotificationShadeWindowView = notificationShadeWindowView;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+        mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mPowerManager = powerManager;
         mVibratorHelper = vibratorHelper;
         mVibratorOptional = vibratorOptional;
@@ -509,14 +512,8 @@
 
     @Override
     public void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) {
-        mStatusBar.setTopHidesStatusBar(topAppHidesStatusBar);
-        if (!topAppHidesStatusBar && mStatusBar.getWereIconsJustHidden()) {
-            // Immediately update the icon hidden state, since that should only apply if we're
-            // staying fullscreen.
-            mStatusBar.setWereIconsJustHidden(false);
-            mCommandQueue.recomputeDisableFlags(mDisplayId, true);
-        }
-        mStatusBar.updateHideIconsForBouncer(true /* animate */);
+        mStatusBarHideIconsForBouncerManager
+                .setTopAppHidesStatusBarAndTriggerUpdate(topAppHidesStatusBar);
     }
 
     @Override
@@ -534,13 +531,11 @@
             if (StatusBar.DEBUG_WINDOW_STATE) {
                 Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state));
             }
-            if (mStatusBar.getStatusBarView() != null) {
-                if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) {
+            if (mStatusBar.getStatusBarView() != null
+                    && !showing
+                    && mStatusBarStateController.getState() == StatusBarState.SHADE) {
                     mNotificationPanelViewController.collapsePanel(
                             false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */);
-                }
-
-                mStatusBar.updateHideIconsForBouncer(false /* animate */);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
new file mode 100644
index 0000000..d2181d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
@@ -0,0 +1,133 @@
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * A class that manages if the status bar (clock + notifications + signal cluster) should be visible
+ * or not when showing the bouncer.
+ *
+ * We want to hide it when:
+ * • User swipes up on the keyguard
+ * • Locked activity that doesn't show a status bar requests the bouncer.
+ *
+ * [getShouldHideStatusBarIconsForBouncer] is the main exported method for this class. The other
+ * methods set state variables that are used in the calculation or manually trigger an update.
+ */
+@SysUISingleton
+class StatusBarHideIconsForBouncerManager @Inject constructor(
+    private val commandQueue: CommandQueue,
+    @Main private val mainExecutor: DelayableExecutor,
+    dumpManager: DumpManager
+) : Dumpable {
+    // State variables set by external classes.
+    private var panelExpanded: Boolean = false
+    private var isOccluded: Boolean = false
+    private var bouncerShowing: Boolean = false
+    private var topAppHidesStatusBar: Boolean = false
+    private var statusBarWindowHidden: Boolean = false
+    private var displayId: Int = 0
+
+    // State variables calculated internally.
+    private var hideIconsForBouncer: Boolean = false
+    private var bouncerWasShowingWhenHidden: Boolean = false
+    private var wereIconsJustHidden: Boolean = false
+
+    init {
+        dumpManager.registerDumpable(this)
+    }
+
+    /** Returns true if the status bar icons should be hidden in the bouncer. */
+    fun getShouldHideStatusBarIconsForBouncer(): Boolean {
+        return hideIconsForBouncer || wereIconsJustHidden
+    }
+
+    fun setStatusBarWindowHidden(statusBarWindowHidden: Boolean) {
+        this.statusBarWindowHidden = statusBarWindowHidden
+    }
+
+    fun setDisplayId(displayId: Int) {
+        this.displayId = displayId
+    }
+
+    fun setPanelExpandedAndTriggerUpdate(panelExpanded: Boolean) {
+        this.panelExpanded = panelExpanded
+        updateHideIconsForBouncer(animate = false)
+    }
+
+    fun setIsOccludedAndTriggerUpdate(isOccluded: Boolean) {
+        this.isOccluded = isOccluded
+        updateHideIconsForBouncer(animate = false)
+    }
+
+    fun setBouncerShowingAndTriggerUpdate(bouncerShowing: Boolean) {
+        this.bouncerShowing = bouncerShowing
+        updateHideIconsForBouncer(animate = true)
+    }
+
+    fun setTopAppHidesStatusBarAndTriggerUpdate(topAppHidesStatusBar: Boolean) {
+        this.topAppHidesStatusBar = topAppHidesStatusBar
+        if (!topAppHidesStatusBar && wereIconsJustHidden) {
+            // Immediately update the icon hidden state, since that should only apply if we're
+            // staying fullscreen.
+            wereIconsJustHidden = false
+            commandQueue.recomputeDisableFlags(displayId, /* animate= */ true)
+        }
+        updateHideIconsForBouncer(animate = true)
+    }
+
+    /**
+     * Updates whether the status bar icons should be hidden in the bouncer. May trigger
+     * [commandQueue.recomputeDisableFlags] if the icon visibility status changes.
+     */
+    fun updateHideIconsForBouncer(animate: Boolean) {
+        val hideBecauseApp =
+            topAppHidesStatusBar &&
+                    isOccluded &&
+                    (statusBarWindowHidden || bouncerShowing)
+        val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing
+        val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard
+        if (hideIconsForBouncer != shouldHideIconsForBouncer) {
+            hideIconsForBouncer = shouldHideIconsForBouncer
+            if (!shouldHideIconsForBouncer && bouncerWasShowingWhenHidden) {
+                // We're delaying the showing, since most of the time the fullscreen app will
+                // hide the icons again and we don't want them to fade in and out immediately again.
+                wereIconsJustHidden = true
+                mainExecutor.executeDelayed(
+                    {
+                        wereIconsJustHidden = false
+                        commandQueue.recomputeDisableFlags(displayId, true)
+                    },
+                    500
+                )
+            } else {
+                commandQueue.recomputeDisableFlags(displayId, animate)
+            }
+        }
+        if (shouldHideIconsForBouncer) {
+            bouncerWasShowingWhenHidden = bouncerShowing
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("---- State variables set externally ----")
+        pw.println("panelExpanded=$panelExpanded")
+        pw.println("isOccluded=$isOccluded")
+        pw.println("bouncerShowing=$bouncerShowing")
+        pw.println("topAppHideStatusBar=$topAppHidesStatusBar")
+        pw.println("statusBarWindowHidden=$statusBarWindowHidden")
+        pw.println("displayId=$displayId")
+
+        pw.println("---- State variables calculated internally ----")
+        pw.println("hideIconsForBouncer=$hideIconsForBouncer")
+        pw.println("bouncerWasShowingWhenHidden=$bouncerWasShowingWhenHidden")
+        pw.println("wereIconsJustHidden=$wereIconsJustHidden")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index dba3b24..c2e790f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -68,8 +68,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
@@ -97,6 +97,7 @@
 
     private final NotificationEntryManager mEntryManager;
     private final NotifPipeline mNotifPipeline;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final ActivityStarter mActivityStarter;
     private final NotificationClickNotifier mClickNotifier;
@@ -136,6 +137,7 @@
             Executor uiBgExecutor,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
+            NotificationVisibilityProvider visibilityProvider,
             HeadsUpManagerPhone headsUpManager,
             ActivityStarter activityStarter,
             NotificationClickNotifier clickNotifier,
@@ -171,6 +173,7 @@
         mUiBgExecutor = uiBgExecutor;
         mEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
+        mVisibilityProvider = visibilityProvider;
         mHeadsUpManager = headsUpManager;
         mActivityStarter = activityStarter;
         mClickNotifier = clickNotifier;
@@ -366,10 +369,7 @@
             mAssistManagerLazy.get().hideAssist();
         }
 
-        NotificationVisibility.NotificationLocation location =
-                NotificationLogger.getNotificationLocation(entry);
-        final NotificationVisibility nv = NotificationVisibility.obtain(entry.getKey(),
-                entry.getRanking().getRank(), getVisibleNotificationsCount(), true, location);
+        final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
 
         // retrieve the group summary to remove with this entry before we tell NMS the
         // notification was clicked to avoid a race condition
@@ -414,10 +414,7 @@
     public void onDragSuccess(NotificationEntry entry) {
         // this method is not responsible for intent sending.
         // will focus follow operation only after drag-and-drop that notification.
-        NotificationVisibility.NotificationLocation location =
-                NotificationLogger.getNotificationLocation(entry);
-        final NotificationVisibility nv = NotificationVisibility.obtain(entry.getKey(),
-                entry.getRanking().getRank(), getVisibleNotificationsCount(), true, location);
+        final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
 
         // retrieve the group summary to remove with this entry before we tell NMS the
         // notification was clicked to avoid a race condition
@@ -681,6 +678,7 @@
         private final Executor mUiBgExecutor;
         private final NotificationEntryManager mEntryManager;
         private final NotifPipeline mNotifPipeline;
+        private final NotificationVisibilityProvider mVisibilityProvider;
         private final HeadsUpManagerPhone mHeadsUpManager;
         private final ActivityStarter mActivityStarter;
         private final NotificationClickNotifier mClickNotifier;
@@ -719,6 +717,7 @@
                 @UiBackground Executor uiBgExecutor,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
+                NotificationVisibilityProvider visibilityProvider,
                 HeadsUpManagerPhone headsUpManager,
                 ActivityStarter activityStarter,
                 NotificationClickNotifier clickNotifier,
@@ -749,6 +748,7 @@
             mUiBgExecutor = uiBgExecutor;
             mEntryManager = entryManager;
             mNotifPipeline = notifPipeline;
+            mVisibilityProvider = visibilityProvider;
             mHeadsUpManager = headsUpManager;
             mActivityStarter = activityStarter;
             mClickNotifier = clickNotifier;
@@ -813,6 +813,7 @@
                     mUiBgExecutor,
                     mEntryManager,
                     mNotifPipeline,
+                    mVisibilityProvider,
                     mHeadsUpManager,
                     mActivityStarter,
                     mClickNotifier,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index fbd9ef7..9c69f51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
 import android.telephony.SubscriptionInfo;
@@ -26,11 +27,11 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.connectivity.IconState;
+import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -44,7 +45,7 @@
 
 /** Controls the signal policies for icons shown in the StatusBar. **/
 @SysUISingleton
-public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
+public class StatusBarSignalPolicy implements SignalCallback,
         SecurityController.SecurityControllerCallback, Tunable {
     private static final String TAG = "StatusBarSignalPolicy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -169,7 +170,7 @@
     }
 
     @Override
-    public void setWifiIndicators(WifiIndicators indicators) {
+    public void setWifiIndicators(@NonNull WifiIndicators indicators) {
         if (DEBUG) {
             Log.d(TAG, "setWifiIndicators: " + indicators);
         }
@@ -219,7 +220,7 @@
     }
 
     @Override
-    public void setCallIndicator(IconState statusIcon, int subId) {
+    public void setCallIndicator(@NonNull IconState statusIcon, int subId) {
         if (DEBUG) {
             Log.d(TAG, "setCallIndicator: "
                     + "statusIcon = " + statusIcon + ","
@@ -247,7 +248,7 @@
     }
 
     @Override
-    public void setMobileDataIndicators(MobileDataIndicators indicators) {
+    public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
         if (DEBUG) {
             Log.d(TAG, "setMobileDataIndicators: " + indicators);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 5a3cb6f..3259f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
@@ -224,6 +225,7 @@
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
             StatusBarIconController statusBarIconController,
+            StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             LockscreenShadeTransitionController transitionController,
             FeatureFlags featureFlags,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -322,6 +324,7 @@
                 animationScheduler,
                 locationPublisher,
                 statusBarIconController,
+                statusBarHideIconsForBouncerManager,
                 transitionController,
                 featureFlags,
                 keyguardUnlockAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 9290879..b6a96a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.connectivity.AccessPointControllerImpl;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
@@ -135,7 +136,7 @@
 
     /** */
     @Binds
-    NetworkController.AccessPointController provideAccessPointController(
+    AccessPointController provideAccessPointController(
             AccessPointControllerImpl accessPointControllerImpl);
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index cc2c208..65106f1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -41,7 +41,9 @@
  * no objects will get constructed if these parameters are empty.
  */
 @Module(subcomponents = [SysUIUnfoldComponent::class])
-object SysUIUnfoldModule {
+class SysUIUnfoldModule {
+    constructor() {}
+
     @Provides
     @SysUISingleton
     fun provideSysUIUnfoldComponent(
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 291c64d..4aad9b6 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -76,8 +76,8 @@
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -109,6 +109,7 @@
     private final ShadeController mShadeController;
     private final IStatusBarService mBarService;
     private final INotificationManager mNotificationManager;
+    private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final NotificationGroupManagerLegacy mNotificationGroupManager;
     private final NotificationEntryManager mNotificationEntryManager;
@@ -132,6 +133,7 @@
             ConfigurationController configurationController,
             @Nullable IStatusBarService statusBarService,
             INotificationManager notificationManager,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
@@ -146,6 +148,7 @@
             return new BubblesManager(context, bubblesOptional.get(),
                     notificationShadeWindowController, statusBarStateController, shadeController,
                     configurationController, statusBarService, notificationManager,
+                    visibilityProvider,
                     interruptionStateProvider, zenModeController, notifUserManager,
                     groupManager, entryManager, notifPipeline, sysUiState, featureFlags,
                     dumpManager, sysuiMainExecutor);
@@ -163,6 +166,7 @@
             ConfigurationController configurationController,
             @Nullable IStatusBarService statusBarService,
             INotificationManager notificationManager,
+            NotificationVisibilityProvider visibilityProvider,
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
@@ -178,6 +182,7 @@
         mNotificationShadeWindowController = notificationShadeWindowController;
         mShadeController = shadeController;
         mNotificationManager = notificationManager;
+        mVisibilityProvider = visibilityProvider;
         mNotificationInterruptStateProvider = interruptionStateProvider;
         mNotificationGroupManager = groupManager;
         mNotificationEntryManager = entryManager;
@@ -598,12 +603,7 @@
         return new DismissedByUserStats(
                 DISMISSAL_BUBBLE,
                 DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(
-                        entry.getKey(),
-                        entry.getRanking().getRank(),
-                        mNotificationEntryManager.getActiveNotificationsCount(),
-                        isVisible,
-                        NotificationLogger.getNotificationLocation(entry)));
+                mVisibilityProvider.obtain(entry, isVisible));
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index e53b450..1ee6f70 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -87,7 +87,6 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -170,8 +169,6 @@
     @Mock
     private TelephonyListenerManager mTelephonyListenerManager;
     @Mock
-    private FeatureFlags mFeatureFlags;
-    @Mock
     private InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private LatencyTracker mLatencyTracker;
@@ -179,6 +176,7 @@
     private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
     // Direct executor
     private Executor mBackgroundExecutor = Runnable::run;
+    private Executor mMainExecutor = Runnable::run;
     private TestableLooper mTestableLooper;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private TestableContext mSpiedContext;
@@ -882,6 +880,25 @@
     }
 
     @Test
+    public void testRegisterAuthControllerCallback() {
+        assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isFalse();
+
+        // verify AuthController.Callback is added:
+        ArgumentCaptor<AuthController.Callback> captor = ArgumentCaptor.forClass(
+                AuthController.Callback.class);
+        verify(mAuthController).addCallback(captor.capture());
+        AuthController.Callback callback = captor.getValue();
+
+        // WHEN udfps is now enrolled
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+        callback.onEnrollmentsChanged();
+
+        // THEN isUdfspEnrolled is TRUE
+        assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue();
+    }
+
+
+    @Test
     public void testStartUdfpsServiceBeginsOnKeyguard() {
         // GIVEN
         // - status bar state is on the keyguard
@@ -1060,9 +1077,9 @@
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
                     mBroadcastDispatcher, mDumpManager,
-                    mRingerModeTracker, mBackgroundExecutor,
+                    mRingerModeTracker, mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
-                    mAuthController, mTelephonyListenerManager, mFeatureFlags,
+                    mAuthController, mTelephonyListenerManager,
                     mInteractionJankMonitor, mLatencyTracker);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 5427806..209df6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -60,9 +60,20 @@
         assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
         assertEquals(1, hostDialogContent.childCount)
 
+        // The original dialog content is added to another view that is the same size as the
+        // original dialog window.
         val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup
         assertEquals(1, hostDialogRoot.childCount)
-        assertEquals(dialog.contentView, hostDialogRoot.getChildAt(0))
+
+        val dialogContentParent = hostDialogRoot.getChildAt(0) as ViewGroup
+        assertEquals(1, dialogContentParent.childCount)
+        assertEquals(TestDialog.DIALOG_WIDTH, dialogContentParent.layoutParams.width)
+        assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentParent.layoutParams.height)
+
+        val dialogContent = dialogContentParent.getChildAt(0)
+        assertEquals(dialog.contentView, dialogContent)
+        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.width)
+        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.height)
 
         // Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that
         // it's a ListenableDialog.
@@ -126,6 +137,11 @@
     }
 
     private class TestDialog(context: Context) : Dialog(context), ListenableDialog {
+        companion object {
+            const val DIALOG_WIDTH = 100
+            const val DIALOG_HEIGHT = 200
+        }
+
         private val listeners = hashSetOf<DialogListener>()
         val contentView = View(context)
         var onStartCalled = false
@@ -138,6 +154,7 @@
 
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
+            window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
             setContentView(contentView)
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
deleted file mode 100644
index 52d08dc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-
-import androidx.annotation.BoolRes;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.util.wrapper.BuildInfo;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-@SmallTest
-public class FeatureFlagReaderTest extends SysuiTestCase {
-    @Mock private Resources mResources;
-    @Mock private BuildInfo mBuildInfo;
-    @Mock private DumpManager mDumpManager;
-    @Mock private PluginManager mPluginManager;
-    @Mock private SystemPropertiesHelper mSystemPropertiesHelper;
-    @Mock private FlagReader mFlagReader;
-
-    private FeatureFlagReader mReader;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mSystemPropertiesHelper.getBoolean(anyString(), anyBoolean()))
-                .thenAnswer(invocation -> invocation.getArgument(1));
-
-        defineFlag(FLAG_RESID_0, false);
-        defineFlag(FLAG_RESID_1, true);
-
-        initialize(true, true);
-    }
-
-    private void initialize(boolean isDebuggable, boolean isOverrideable) {
-        when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable);
-        when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable);
-        mReader = new FeatureFlagReader(
-                mResources, mBuildInfo, mDumpManager, mSystemPropertiesHelper, mFlagReader);
-    }
-
-    @Test
-    public void testCantOverrideIfNotDebuggable() {
-        // GIVEN that the build is not debuggable
-        initialize(false, true);
-
-        // GIVEN that a flag has been overridden to true
-        overrideFlag(FLAG_RESID_0, true);
-
-        // THEN the flag is still false
-        assertFalse(mReader.isEnabled(FLAG_RESID_0));
-    }
-
-    @Test
-    public void testCantOverrideIfNotOverrideable() {
-        // GIVEN that flags are not overrideable
-        initialize(true, false);
-
-        // GIVEN that a flag has been overridden to true
-        overrideFlag(FLAG_RESID_0, true);
-
-        // THEN the flag is still false
-        assertFalse(mReader.isEnabled(FLAG_RESID_0));
-    }
-
-    @Test
-    public void testReadFlags() {
-        assertFalse(mReader.isEnabled(FLAG_RESID_0));
-        assertTrue(mReader.isEnabled(FLAG_RESID_1));
-    }
-
-    @Test
-    public void testOverrideFlags() {
-        // GIVEN that flags are overridden
-        overrideFlag(FLAG_RESID_0, true);
-        overrideFlag(FLAG_RESID_1, false);
-
-        // THEN the reader returns the overridden values
-        assertTrue(mReader.isEnabled(FLAG_RESID_0));
-        assertFalse(mReader.isEnabled(FLAG_RESID_1));
-    }
-
-    @Test
-    public void testThatFlagReadsAreCached() {
-        // GIVEN that a flag is overridden
-        overrideFlag(FLAG_RESID_0, true);
-
-        // WHEN the flag is queried many times
-        mReader.isEnabled(FLAG_RESID_0);
-        mReader.isEnabled(FLAG_RESID_0);
-        mReader.isEnabled(FLAG_RESID_0);
-        mReader.isEnabled(FLAG_RESID_0);
-
-        // THEN the underlying resource and override are only queried once
-        verify(mResources, times(1)).getBoolean(FLAG_RESID_0);
-        verify(mSystemPropertiesHelper, times(1))
-                .getBoolean(fakeStorageKey(FLAG_RESID_0), false);
-    }
-
-    @Test
-    public void testDump() {
-        // GIVEN that the flag 0 (by override) and 1 (by default) are both true
-        overrideFlag(FLAG_RESID_0, true);
-
-        // WHEN the flags have been accessed
-        assertTrue(mReader.isEnabled(FLAG_RESID_0));
-        assertTrue(mReader.isEnabled(FLAG_RESID_1));
-
-        // THEN the dump contains the flags and their correct values
-        StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
-        mReader.dump(mock(FileDescriptor.class), pw, new String[0]);
-        pw.flush();
-        String dump = sw.toString();
-        assertThat(dump).contains(" flag_testname_" + FLAG_RESID_0 + ": true\n");
-        assertThat(dump).contains(" flag_testname_" + FLAG_RESID_1 + ": true\n");
-        assertThat(dump).contains("AreFlagsOverrideable: true\n");
-    }
-
-    private void defineFlag(int resId, boolean value) {
-        when(mResources.getBoolean(resId)).thenReturn(value);
-        when(mResources.getResourceEntryName(resId)).thenReturn(fakeResourceEntryName(resId));
-    }
-
-    private void overrideFlag(int resId, boolean value) {
-        when(mSystemPropertiesHelper.getBoolean(eq(fakeStorageKey(resId)), anyBoolean()))
-                .thenReturn(value);
-    }
-
-    private String fakeResourceEntryName(@BoolRes int resId) {
-        return "flag_testname_" + resId;
-    }
-
-    private String fakeStorageKey(@BoolRes int resId) {
-        return "persist.systemui.flag_testname_" + resId;
-    }
-
-    private static final int FLAG_RESID_0 = 47;
-    private static final int FLAG_RESID_1 = 48;
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
index a850f70..30e9b51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
@@ -18,7 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
 
 import androidx.test.filters.SmallTest;
 
@@ -29,11 +34,13 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 @SmallTest
 public class FeatureFlagsTest extends SysuiTestCase {
 
-    @Mock FeatureFlagReader mFeatureFlagReader;
+    @Mock Resources mResources;
+    @Mock FlagReader mFeatureFlagReader;
 
     private FeatureFlags mFeatureFlags;
 
@@ -41,7 +48,10 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlags = new FeatureFlags(mFeatureFlagReader, getContext());
+        when(mFeatureFlagReader.isEnabled(anyInt(), anyBoolean())).thenAnswer(
+                (Answer<Boolean>) invocation -> invocation.getArgument(1));
+
+        mFeatureFlags = new FeatureFlags(mResources, mFeatureFlagReader, getContext());
     }
 
     @Test
@@ -103,6 +113,26 @@
         pluginListener.onFlagChanged(flag.getId());
         // Assert that the change was not triggered
         assertThat(changedFlag[0]).isNull();
+    }
 
+    @Test
+    public void testBooleanDefault() {
+        BooleanFlag flag = new BooleanFlag(1, true);
+
+        mFeatureFlags.addFlag(flag);
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testBooleanResourceOverlay() {
+        int resourceId = 12;
+        BooleanFlag flag = new BooleanFlag(1, false, resourceId);
+        when(mResources.getBoolean(resourceId)).thenReturn(true);
+        when(mResources.getResourceEntryName(resourceId)).thenReturn("flag");
+
+        mFeatureFlags.addFlag(flag);
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
index d8ba164..99f21ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
@@ -21,7 +21,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -43,8 +45,9 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.wm.shell.bubbles.Bubble;
 
@@ -76,7 +79,9 @@
     private LaunchConversationActivity mActivity;
 
     @Mock
-    private NotificationEntryManager mNotificationEntryManager;
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
+    private CommonNotifCollection mNotifCollection;
     @Mock
     private IStatusBarService mIStatusBarService;
     @Mock
@@ -104,8 +109,13 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mActivity = new LaunchConversationActivity(mNotificationEntryManager,
-                Optional.of(mBubblesManager), mUserManager, mCommandQueue);
+        mActivity = new LaunchConversationActivity(
+                mVisibilityProvider,
+                mNotifCollection,
+                Optional.of(mBubblesManager),
+                mUserManager,
+                mCommandQueue
+        );
         verify(mCommandQueue, times(1)).addCallback(mCallbacksCaptor.capture());
 
         mActivity.setIsForTesting(true, mIStatusBarService);
@@ -114,19 +124,26 @@
         mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, PACKAGE_NAME);
         mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, USER_HANDLE);
 
-        when(mNotificationEntryManager.getActiveNotificationsCount()).thenReturn(NOTIF_COUNT);
-        when(mNotificationEntryManager.getPendingOrActiveNotif(NOTIF_KEY)).thenReturn(mNotifEntry);
-        when(mNotificationEntryManager.getPendingOrActiveNotif(NOTIF_KEY_NO_ENTRY))
-                .thenReturn(null);
-        when(mNotificationEntryManager.getPendingOrActiveNotif(NOTIF_KEY_NO_RANKING))
-                .thenReturn(mNotifEntryNoRanking);
-        when(mNotificationEntryManager.getPendingOrActiveNotif(NOTIF_KEY_CAN_BUBBLE))
-                .thenReturn(mNotifEntryCanBubble);
+        when(mNotifCollection.getEntry(NOTIF_KEY)).thenReturn(mNotifEntry);
+        when(mNotifCollection.getEntry(NOTIF_KEY_NO_ENTRY)).thenReturn(null);
+        when(mNotifCollection.getEntry(NOTIF_KEY_NO_RANKING)).thenReturn(mNotifEntryNoRanking);
+        when(mNotifCollection.getEntry(NOTIF_KEY_CAN_BUBBLE)).thenReturn(mNotifEntryCanBubble);
+        when(mVisibilityProvider.obtain(anyString(), anyBoolean())).thenAnswer(
+                invocation-> {
+                    String key = invocation.getArgument(0);
+                    boolean visible = invocation.getArgument(1);
+                    return NotificationVisibility.obtain(key, NOTIF_RANK, NOTIF_COUNT, visible);
+                });
+        when(mVisibilityProvider.obtain(any(NotificationEntry.class), anyBoolean())).thenAnswer(
+                invocation-> {
+                    String key = invocation.<NotificationEntry>getArgument(0).getKey();
+                    boolean visible = invocation.getArgument(1);
+                    return NotificationVisibility.obtain(key, NOTIF_RANK, NOTIF_COUNT, visible);
+                });
         when(mNotifEntry.getRanking()).thenReturn(mRanking);
         when(mNotifEntryCanBubble.getRanking()).thenReturn(mRanking);
         when(mNotifEntryCanBubble.canBubble()).thenReturn(true);
         when(mNotifEntryNoRanking.getRanking()).thenReturn(null);
-        when(mRanking.getRank()).thenReturn(NOTIF_RANK);
         when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
     }
 
@@ -173,8 +190,12 @@
         assertThat(mActivity.isFinishing()).isTrue();
         mCallbacksCaptor.getValue().appTransitionFinished(DEFAULT_DISPLAY);
 
+        // Ensure callback removed
+        verify(mCommandQueue).removeCallback(any());
+        // Clear the notification for bubbles.
         verify(mIStatusBarService, times(1)).onNotificationClear(any(),
                 anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
+        // Do not select the bubble.
         verify(mBubblesManager, never()).expandStackAndSelectBubble(any(Bubble.class));
         verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class));
 
@@ -194,6 +215,8 @@
         assertThat(mActivity.isFinishing()).isTrue();
         mCallbacksCaptor.getValue().appTransitionFinished(DEFAULT_DISPLAY);
 
+        // Ensure callback removed
+        verify(mCommandQueue).removeCallback(any());
         // Don't clear the notification for bubbles.
         verify(mIStatusBarService, never()).onNotificationClear(any(),
                 anyInt(), any(), anyInt(), anyInt(), any());
@@ -211,10 +234,14 @@
         mActivity.onCreate(new Bundle());
 
         assertThat(mActivity.isFinishing()).isTrue();
-        mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
+        mCallbacksCaptor.getValue().appTransitionFinished(DEFAULT_DISPLAY);
 
+        // Ensure callback removed
+        verify(mCommandQueue).removeCallback(any());
+        // Don't clear the notification for bubbles.
         verify(mIStatusBarService, never()).onNotificationClear(any(),
                 anyInt(), any(), anyInt(), anyInt(), any());
+        // Do not select the bubble.
         verify(mBubblesManager, never()).expandStackAndSelectBubble(any(Bubble.class));
         verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class));
     }
@@ -233,6 +260,9 @@
         assertThat(mActivity.isFinishing()).isTrue();
         mCallbacksCaptor.getValue().appTransitionFinished(DEFAULT_DISPLAY);
 
+        // Ensure callback removed
+        verify(mCommandQueue).removeCallback(any());
+        // Select the bubble.
         verify(mBubblesManager, times(1)).expandStackAndSelectBubble(eq(bubble));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 8ae7100..bd794d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -39,8 +39,10 @@
 import com.android.keyguard.CarrierTextManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.connectivity.IconState;
+import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 import com.android.systemui.utils.os.FakeHandler;
@@ -57,7 +59,7 @@
 public class QSCarrierGroupControllerTest extends LeakCheckedTest {
 
     private QSCarrierGroupController mQSCarrierGroupController;
-    private NetworkController.SignalCallback mSignalCallback;
+    private SignalCallback mSignalCallback;
     private CarrierTextManager.CarrierTextCallback mCallback;
     @Mock
     private QSCarrierGroup mQSCarrierGroup;
@@ -94,7 +96,7 @@
         when(mNetworkController.hasVoiceCallingFeature()).thenReturn(true);
         doAnswer(invocation -> mSignalCallback = invocation.getArgument(0))
                 .when(mNetworkController)
-                .addCallback(any(NetworkController.SignalCallback.class));
+                .addCallback(any(SignalCallback.class));
 
         when(mCarrierTextControllerBuilder.setShowAirplaneMode(anyBoolean()))
                 .thenReturn(mCarrierTextControllerBuilder);
@@ -230,8 +232,8 @@
         mSlotIndexResolver.overrideInvalid = true;
 
         MobileDataIndicators indicators = new MobileDataIndicators(
-                mock(NetworkController.IconState.class),
-                mock(NetworkController.IconState.class),
+                mock(IconState.class),
+                mock(IconState.class),
                 0, 0, true, true, "", "", "", 0, true, true);
         mSignalCallback.setMobileDataIndicators(indicators);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index f0bd0657..5a49337 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,8 +43,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -77,7 +79,7 @@
     @Mock
     private QSTileHost mHost;
     @Mock
-    NetworkController.SignalCallback mSignalCallback;
+    SignalCallback mSignalCallback;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
@@ -122,8 +124,8 @@
         mTestableLooper.processAllMessages();
 
         mCastTile.handleSetListening(true);
-        ArgumentCaptor<NetworkController.SignalCallback> signalCallbackArgumentCaptor =
-                ArgumentCaptor.forClass(NetworkController.SignalCallback.class);
+        ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(SignalCallback.class);
         verify(mNetworkController).observe(any(LifecycleOwner.class),
                 signalCallbackArgumentCaptor.capture());
         mSignalCallback = signalCallbackArgumentCaptor.getValue();
@@ -139,10 +141,9 @@
     // All these tests for enabled/disabled wifi have hotspot not enabled
     @Test
     public void testStateUnavailable_wifiDisabled() {
-        NetworkController.IconState qsIcon =
-                new NetworkController.IconState(false, 0, "");
+        IconState qsIcon = new IconState(false, 0, "");
         WifiIndicators indicators = new WifiIndicators(
-                false, mock(NetworkController.IconState.class),
+                false, mock(IconState.class),
                 qsIcon, false,false, "",
                 false, "");
         mSignalCallback.setWifiIndicators(indicators);
@@ -153,10 +154,9 @@
 
     @Test
     public void testStateUnavailable_wifiNotConnected() {
-        NetworkController.IconState qsIcon =
-                new NetworkController.IconState(false, 0, "");
+        IconState qsIcon = new IconState(false, 0, "");
         WifiIndicators indicators = new WifiIndicators(
-                true, mock(NetworkController.IconState.class),
+                true, mock(IconState.class),
                 qsIcon, false,false, "",
                 false, "");
         mSignalCallback.setWifiIndicators(indicators);
@@ -166,10 +166,9 @@
     }
 
     private void enableWifiAndProcessMessages() {
-        NetworkController.IconState qsIcon =
-                new NetworkController.IconState(true, 0, "");
+        IconState qsIcon = new IconState(true, 0, "");
         WifiIndicators indicators = new WifiIndicators(
-                true, mock(NetworkController.IconState.class),
+                true, mock(IconState.class),
                 qsIcon, false,false, "",
                 false, "");
         mSignalCallback.setWifiIndicators(indicators);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 964ce01..e4c5299 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -17,9 +17,11 @@
 package com.android.systemui.qs.tiles;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -34,6 +36,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -41,10 +44,12 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -67,6 +72,10 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private QSLogger mQSLogger;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private DialogLaunchAnimator mDialogLaunchAnimator;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -89,7 +98,9 @@
                 mActivityStarter,
                 mQSLogger,
                 mController,
-                mKeyguardDismissUtil
+                mKeyguardDismissUtil,
+                mKeyguardStateController,
+                mDialogLaunchAnimator
         );
 
         mTile.initialize();
@@ -112,7 +123,15 @@
 
         mTile.handleClick(null /* view */);
         mTestableLooper.processAllMessages();
-        verify(mController, times(1)).getPromptIntent();
+
+        ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class);
+        verify(mController).createScreenRecordDialog(any(), onStartRecordingClicked.capture());
+
+        // When starting the recording, we collapse the shade and disable the dialog animation.
+        assertNotNull(onStartRecordingClicked.getValue());
+        onStartRecordingClicked.getValue().run();
+        verify(mDialogLaunchAnimator).disableAllCurrentDialogsExitAnimations();
+        verify(mHost).collapsePanels();
     }
 
     // Test that the tile is active and labeled correctly when the controller is starting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index eb03b5f..ca8903b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -49,8 +49,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
+import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.toast.SystemUIToast;
@@ -104,7 +103,7 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private NetworkController.AccessPointController mAccessPointController;
+    private AccessPointController mAccessPointController;
     @Mock
     private WifiEntry mConnectedEntry;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index b7cc651dc..013e58e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -32,6 +32,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.UserContextProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,6 +53,8 @@
     private RecordingController.RecordingStateChangeCallback mCallback;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private UserContextProvider mUserContextProvider;
 
     private RecordingController mController;
 
@@ -60,7 +63,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new RecordingController(mBroadcastDispatcher);
+        mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider);
         mController.addCallback(mCallback);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 23cca72..48f8206 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -62,6 +62,8 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -84,8 +86,12 @@
 
     // Dependency mocks:
     @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
     private NotificationEntryManager mEntryManager;
     @Mock
+    private CommonNotifCollection mNotifCollection;
+    @Mock
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
     private NotificationClickNotifier mClickNotifier;
@@ -419,6 +425,8 @@
                     mBroadcastDispatcher,
                     mDevicePolicyManager,
                     mUserManager,
+                    (() -> mVisibilityProvider),
+                    (() -> mNotifCollection),
                     mClickNotifier,
                     NotificationLockscreenUserManagerTest.this.mKeyguardManager,
                     mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 4ed7224..8e4b98f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
@@ -70,7 +71,7 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private RemoteInputController.Delegate mDelegate;
     @Mock private NotificationRemoteInputManager.Callback mCallback;
     @Mock private RemoteInputController mController;
@@ -101,6 +102,7 @@
                 mock(FeatureFlags.class),
                 mLockscreenUserManager,
                 mSmartReplyController,
+                mVisibilityProvider,
                 mEntryManager,
                 mock(RemoteInputNotificationRebuilder.class),
                 () -> Optional.of(mock(StatusBar.class)),
@@ -191,6 +193,7 @@
                 FeatureFlags featureFlags,
                 NotificationLockscreenUserManager lockscreenUserManager,
                 SmartReplyController smartReplyController,
+                NotificationVisibilityProvider visibilityProvider,
                 NotificationEntryManager notificationEntryManager,
                 RemoteInputNotificationRebuilder rebuilder,
                 Lazy<Optional<StatusBar>> statusBarOptionalLazy,
@@ -205,6 +208,7 @@
                     featureFlags,
                     lockscreenUserManager,
                     smartReplyController,
+                    visibilityProvider,
                     notificationEntryManager,
                     rebuilder,
                     statusBarOptionalLazy,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 99c965a..8b28fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 
@@ -71,7 +72,7 @@
     private SmartReplyController mSmartReplyController;
     private NotificationRemoteInputManager mRemoteInputManager;
 
-    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private RemoteInputController.Delegate mDelegate;
     @Mock private NotificationRemoteInputManager.Callback mCallback;
     @Mock private StatusBarNotification mSbn;
@@ -89,7 +90,7 @@
 
         mSmartReplyController = new SmartReplyController(
                 mock(DumpManager.class),
-                mNotificationEntryManager,
+                mVisibilityProvider,
                 mIStatusBarService,
                 mClickNotifier);
         mDependency.injectTestDependency(SmartReplyController.class,
@@ -97,7 +98,9 @@
 
         mRemoteInputManager = new NotificationRemoteInputManager(mContext,
                 mock(FeatureFlags.class),
-                mock(NotificationLockscreenUserManager.class), mSmartReplyController,
+                mock(NotificationLockscreenUserManager.class),
+                mSmartReplyController,
+                mVisibilityProvider,
                 mNotificationEntryManager,
                 new RemoteInputNotificationRebuilder(mContext),
                 () -> Optional.of(mock(StatusBar.class)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 7896a26..c57b64d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -56,9 +56,9 @@
     @Mock
     private lateinit var wifiPickerTracker: WifiPickerTracker
     @Mock
-    private lateinit var callback: NetworkController.AccessPointController.AccessPointCallback
+    private lateinit var callback: AccessPointController.AccessPointCallback
     @Mock
-    private lateinit var otherCallback: NetworkController.AccessPointController.AccessPointCallback
+    private lateinit var otherCallback: AccessPointController.AccessPointCallback
     @Mock
     private lateinit var wifiEntryConnected: WifiEntry
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 11a53c5..2d29c80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -29,10 +29,6 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
 import com.android.systemui.tests.R;
 
 import org.junit.Before;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
similarity index 90%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
index 92a32bc..7ddfde3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
@@ -14,22 +14,26 @@
  * limitations under the License.
  */
 
-package com.android.settingslib;
+package com.android.systemui.statusbar.connectivity;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+
 import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 
-@RunWith(RobolectricTestRunner.class)
-public class MobileStateTest {
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MobileStateTest extends SysuiTestCase {
 
-    private SignalIcon.MobileState mState = new SignalIcon.MobileState();
+    private final MobileState mState = new MobileState();
 
     @Before
     public void setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index b23d07a..47a11fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -72,9 +72,6 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.telephony.TelephonyListenerManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
index 675d755..f6f939a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
@@ -23,8 +23,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
-import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index ffeaf20..a39971d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -36,7 +36,6 @@
 import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.settingslib.mobile.TelephonyIcons;
-import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
new file mode 100644
index 0000000..cf7174e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotifPipelineTest : SysuiTestCase() {
+
+    @Mock private lateinit var notifCollection: NotifCollection
+    @Mock private lateinit var shadeListBuilder: ShadeListBuilder
+    private lateinit var notifPipeline: NotifPipeline
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        notifPipeline = NotifPipeline(notifCollection, shadeListBuilder)
+        whenever(shadeListBuilder.shadeList).thenReturn(listOf(
+                NotificationEntryBuilder().setPkg("foo").setId(1).build(),
+                NotificationEntryBuilder().setPkg("foo").setId(2).build(),
+                group(
+                        NotificationEntryBuilder().setPkg("bar").setId(1).build(),
+                        NotificationEntryBuilder().setPkg("bar").setId(2).build(),
+                        NotificationEntryBuilder().setPkg("bar").setId(3).build(),
+                        NotificationEntryBuilder().setPkg("bar").setId(4).build()
+                ),
+                NotificationEntryBuilder().setPkg("baz").setId(1).build()
+        ))
+    }
+
+    private fun group(summary: NotificationEntry, vararg children: NotificationEntry): GroupEntry {
+        return GroupEntry(summary.key, summary.creationTime).also { group ->
+            group.summary = summary
+            for (it in children) {
+                group.addChild(it)
+            }
+        }
+    }
+
+    @Test
+    fun testGetShadeListCount() {
+        assertThat(notifPipeline.getShadeListCount()).isEqualTo(7)
+    }
+
+    @Test
+    fun testGetFlatShadeList() {
+        assertThat(notifPipeline.getFlatShadeList().map { it.key }).containsExactly(
+                "0|foo|1|null|0",
+                "0|foo|2|null|0",
+                "0|bar|1|null|0",
+                "0|bar|2|null|0",
+                "0|bar|3|null|0",
+                "0|bar|4|null|0",
+                "0|baz|1|null|0"
+        ).inOrder()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 711f0ba..a46b440 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -29,12 +29,12 @@
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.util.mockito.withArgCaptor
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -65,9 +65,9 @@
         coordinator.attach(pipeline)
 
         // capture arguments:
-        val notifPromoterCaptor = ArgumentCaptor.forClass(NotifPromoter::class.java)
-        verify(pipeline).addPromoter(notifPromoterCaptor.capture())
-        promoter = notifPromoterCaptor.value
+        promoter = withArgCaptor {
+            verify(pipeline).addPromoter(capture())
+        }
 
         peopleSectioner = coordinator.sectioner
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
index 0cba0703..0f6bd77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
 import com.android.systemui.statusbar.notification.row.NotificationGuts
-import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -60,13 +60,11 @@
         initMocks(this)
         coordinator = GutsCoordinator(notifGutsViewManager, logger, dumpManager)
         coordinator.attach(pipeline)
-        notifLifetimeExtender = argumentCaptor<NotifLifetimeExtender>().let {
-            verify(pipeline).addNotificationLifetimeExtender(it.capture())
-            it.value!!
+        notifLifetimeExtender = withArgCaptor {
+            verify(pipeline).addNotificationLifetimeExtender(capture())
         }
-        notifGutsViewListener = argumentCaptor<NotifGutsViewListener>().let {
-            verify(notifGutsViewManager).setGutsListener(it.capture())
-            it.value!!
+        notifGutsViewListener = withArgCaptor {
+            verify(notifGutsViewManager).setGutsListener(capture())
         }
         notifLifetimeExtender.setCallback(lifetimeExtenderCallback)
         entry1 = NotificationEntryBuilder().setId(1).build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
index 5915cd7..452af21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.withArgCaptor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -56,13 +56,11 @@
         initMocks(this)
         coordinator = ShadeEventCoordinator(logger)
         coordinator.attach(pipeline)
-        notifCollectionListener = argumentCaptor<NotifCollectionListener>().let {
-            verify(pipeline).addCollectionListener(it.capture())
-            it.value!!
+        notifCollectionListener = withArgCaptor {
+            verify(pipeline).addCollectionListener(capture())
         }
-        onBeforeRenderListListener = argumentCaptor<OnBeforeRenderListListener>().let {
-            verify(pipeline).addOnBeforeRenderListListener(it.capture())
-            it.value!!
+        onBeforeRenderListListener = withArgCaptor {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
         }
         coordinator.setNotifRemovedByUserCallback(notifRemovedByUserCallback)
         coordinator.setShadeEmptiedCallback(shadeEmptiedCallback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
index a8db8d7..fdff6e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
@@ -35,15 +35,13 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyString
@@ -69,15 +67,6 @@
     @Mock
     private lateinit var pluggableListener: Pluggable.PluggableListener<NotifFilter>
 
-    @Captor
-    private lateinit var filterCaptor: ArgumentCaptor<NotifFilter>
-    @Captor
-    private lateinit var collectionListenerCaptor: ArgumentCaptor<NotifCollectionListener>
-    @Captor
-    private lateinit var stateListenerCaptor: ArgumentCaptor<StatusBarStateController.StateListener>
-    @Captor
-    private lateinit var smartspaceListenerCaptor: ArgumentCaptor<SmartspaceTargetListener>
-
     private lateinit var filter: NotifFilter
     private lateinit var collectionListener: NotifCollectionListener
     private lateinit var statusBarListener: StatusBarStateController.StateListener
@@ -118,18 +107,22 @@
         // Attach the deduper and capture the listeners/filters that it registers
         deduper.attach(notifPipeline)
 
-        verify(notifPipeline).addPreGroupFilter(filterCaptor.capture())
-        filter = filterCaptor.value
+        filter = withArgCaptor {
+            verify(notifPipeline).addPreGroupFilter(capture())
+        }
         filter.setInvalidationListener(pluggableListener)
 
-        verify(notifPipeline).addCollectionListener(capture(collectionListenerCaptor))
-        collectionListener = collectionListenerCaptor.value
+        collectionListener = withArgCaptor {
+            verify(notifPipeline).addCollectionListener(capture())
+        }
 
-        verify(statusBarStateController).addCallback(capture(stateListenerCaptor))
-        statusBarListener = stateListenerCaptor.value
+        statusBarListener = withArgCaptor {
+            verify(statusBarStateController).addCallback(capture())
+        }
 
-        verify(smartspaceController).addListener(capture(smartspaceListenerCaptor))
-        newTargetListener = smartspaceListenerCaptor.value
+        newTargetListener = withArgCaptor {
+            verify(smartspaceController).addListener(capture())
+        }
 
         // Initialize some test data
         entry1HasRecentlyAlerted = NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
new file mode 100644
index 0000000..4cf530e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2017 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.notification.logging;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.logging.nano.Notifications;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationLoggerLegacyTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final int TEST_UID = 0;
+
+    @Mock private NotificationListContainer mListContainer;
+    @Mock private IStatusBarService mBarService;
+    @Mock private ExpandableNotificationRow mRow;
+    @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
+
+    // Dependency mocks:
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotifPipeline mNotifPipeline;
+    @Mock private NotificationListener mListener;
+
+    private NotificationEntry mEntry;
+    private TestableNotificationLogger mLogger;
+    private ConcurrentLinkedQueue<AssertionError> mErrorQueue = new ConcurrentLinkedQueue<>();
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
+    private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
+            new NotificationPanelLoggerFake();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mEntry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setNotification(new Notification())
+                .setUser(UserHandle.CURRENT)
+                .setInstanceId(InstanceId.fakeInstanceId(1))
+                .build();
+        mEntry.setRow(mRow);
+
+        mLogger = new TestableNotificationLogger(
+                mListener,
+                mUiBgExecutor,
+                mFeatureFlags,
+                mVisibilityProvider,
+                mEntryManager,
+                mNotifPipeline,
+                mock(StatusBarStateControllerImpl.class),
+                mBarService,
+                mExpansionStateLogger
+        );
+        mLogger.setUpWithContainer(mListContainer);
+        verify(mEntryManager).addNotificationEntryListener(any());
+        verify(mNotifPipeline, never()).addCollectionListener(any());
+    }
+
+    @Test
+    public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception {
+        NotificationVisibility[] newlyVisibleKeys = {
+                NotificationVisibility.obtain(mEntry.getKey(), 0, 1, true)
+        };
+        NotificationVisibility[] noLongerVisibleKeys = {};
+        doAnswer(invocation -> {
+                    try {
+                        assertArrayEquals(newlyVisibleKeys,
+                                (NotificationVisibility[]) invocation.getArguments()[0]);
+                        assertArrayEquals(noLongerVisibleKeys,
+                                (NotificationVisibility[]) invocation.getArguments()[1]);
+                    } catch (AssertionError error) {
+                        mErrorQueue.offer(error);
+                    }
+                    return null;
+                }
+        ).when(mBarService).onNotificationVisibilityChanged(any(NotificationVisibility[].class),
+                any(NotificationVisibility[].class));
+
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
+        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        TestableLooper.get(this).processAllMessages();
+        mUiBgExecutor.runAllReady();
+
+        if (!mErrorQueue.isEmpty()) {
+            throw mErrorQueue.poll();
+        }
+
+        // |mEntry| won't change visibility, so it shouldn't be reported again:
+        Mockito.reset(mBarService);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        TestableLooper.get(this).processAllMessages();
+        mUiBgExecutor.runAllReady();
+
+        verify(mBarService, never()).onNotificationVisibilityChanged(any(), any());
+    }
+
+    @Test
+    public void testStoppingNotificationLoggingReportsCurrentNotifications()
+            throws Exception {
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
+        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        TestableLooper.get(this).processAllMessages();
+        mUiBgExecutor.runAllReady();
+        Mockito.reset(mBarService);
+
+        setStateAsleep();
+        mLogger.onDozingChanged(false);  // Wake to lockscreen
+        mLogger.onDozingChanged(true);  // And go back to sleep, turning off logging
+        mUiBgExecutor.runAllReady();
+        // The visibility objects are recycled by NotificationLogger, so we can't use specific
+        // matchers here.
+        verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
+    }
+
+    private void setStateAsleep() {
+        mLogger.onPanelExpandedChanged(true);
+        mLogger.onDozingChanged(true);
+        mLogger.onStateChanged(StatusBarState.KEYGUARD);
+    }
+
+    private void setStateAwake() {
+        mLogger.onPanelExpandedChanged(false);
+        mLogger.onDozingChanged(false);
+        mLogger.onStateChanged(StatusBarState.SHADE);
+    }
+
+    @Test
+    public void testLogPanelShownOnWake() {
+        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        setStateAsleep();
+        mLogger.onDozingChanged(false);  // Wake to lockscreen
+        assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
+        assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen);
+        assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
+        Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
+        assertEquals(TEST_PACKAGE_NAME, n.packageName);
+        assertEquals(TEST_UID, n.uid);
+        assertEquals(1, n.instanceId);
+        assertFalse(n.isGroupSummary);
+        assertEquals(Notifications.Notification.SECTION_ALERTING, n.section);
+    }
+
+    @Test
+    public void testLogPanelShownOnShadePull() {
+        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        setStateAwake();
+        // Now expand panel
+        mLogger.onPanelExpandedChanged(true);
+        assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
+        assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen);
+        assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
+        Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
+        assertEquals(TEST_PACKAGE_NAME, n.packageName);
+        assertEquals(TEST_UID, n.uid);
+        assertEquals(1, n.instanceId);
+        assertFalse(n.isGroupSummary);
+        assertEquals(Notifications.Notification.SECTION_ALERTING, n.section);
+    }
+
+
+    @Test
+    public void testLogPanelShownHandlesNullInstanceIds() {
+        // Construct a NotificationEntry like mEntry, but with a null instance id.
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setNotification(new Notification())
+                .setUser(UserHandle.CURRENT)
+                .build();
+        entry.setRow(mRow);
+
+        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(entry));
+        setStateAsleep();
+        mLogger.onDozingChanged(false);  // Wake to lockscreen
+        assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
+        assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
+        Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
+        assertEquals(0, n.instanceId);
+    }
+
+    private class TestableNotificationLogger extends NotificationLogger {
+
+        TestableNotificationLogger(NotificationListener notificationListener,
+                Executor uiBgExecutor,
+                FeatureFlags featureFlags,
+                NotificationVisibilityProvider visibilityProvider,
+                NotificationEntryManager entryManager,
+                NotifPipeline notifPipeline,
+                StatusBarStateControllerImpl statusBarStateController,
+                IStatusBarService barService,
+                ExpansionStateLogger expansionStateLogger) {
+            super(
+                    notificationListener,
+                    uiBgExecutor,
+                    featureFlags,
+                    visibilityProvider,
+                    entryManager,
+                    notifPipeline,
+                    statusBarStateController,
+                    expansionStateLogger,
+                    mNotificationPanelLoggerFake
+            );
+            mBarService = barService;
+            // Make this on the current thread so we can wait for it during tests.
+            mHandler = Handler.createAsync(Looper.myLooper());
+        }
+
+        OnChildLocationsChangedListener getChildLocationsChangedListenerForTest() {
+            return mNotificationLocationsChangedListener;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index c979dc6..ba198ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -41,13 +41,15 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -59,8 +61,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -81,9 +81,11 @@
     @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
 
     // Dependency mocks:
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotifPipeline mNotifPipeline;
     @Mock private NotificationListener mListener;
-    @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
 
     private NotificationEntry mEntry;
     private TestableNotificationLogger mLogger;
@@ -95,8 +97,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
-        mDependency.injectTestDependency(NotificationListener.class, mListener);
+        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
 
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
@@ -108,11 +109,20 @@
                 .build();
         mEntry.setRow(mRow);
 
-        mLogger = new TestableNotificationLogger(mListener, mUiBgExecutor,
-                mEntryManager, mock(StatusBarStateControllerImpl.class), mBarService,
-                mExpansionStateLogger);
+        mLogger = new TestableNotificationLogger(
+                mListener,
+                mUiBgExecutor,
+                mFeatureFlags,
+                mVisibilityProvider,
+                mEntryManager,
+                mNotifPipeline,
+                mock(StatusBarStateControllerImpl.class),
+                mBarService,
+                mExpansionStateLogger
+        );
         mLogger.setUpWithContainer(mListContainer);
-        verify(mEntryManager).addNotificationEntryListener(mEntryListenerCaptor.capture());
+        verify(mEntryManager, never()).addNotificationEntryListener(any());
+        verify(mNotifPipeline).addCollectionListener(any());
     }
 
     @Test
@@ -136,12 +146,12 @@
                 any(NotificationVisibility[].class));
 
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
 
-        if(!mErrorQueue.isEmpty()) {
+        if (!mErrorQueue.isEmpty()) {
             throw mErrorQueue.poll();
         }
 
@@ -158,7 +168,7 @@
     public void testStoppingNotificationLoggingReportsCurrentNotifications()
             throws Exception {
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -187,7 +197,7 @@
 
     @Test
     public void testLogPanelShownOnWake() {
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -203,7 +213,7 @@
 
     @Test
     public void testLogPanelShownOnShadePull() {
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
         setStateAwake();
         // Now expand panel
         mLogger.onPanelExpandedChanged(true);
@@ -231,7 +241,7 @@
                 .build();
         entry.setRow(mRow);
 
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(entry));
+        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(entry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -244,19 +254,30 @@
 
         TestableNotificationLogger(NotificationListener notificationListener,
                 Executor uiBgExecutor,
+                FeatureFlags featureFlags,
+                NotificationVisibilityProvider visibilityProvider,
                 NotificationEntryManager entryManager,
+                NotifPipeline notifPipeline,
                 StatusBarStateControllerImpl statusBarStateController,
                 IStatusBarService barService,
                 ExpansionStateLogger expansionStateLogger) {
-            super(notificationListener, uiBgExecutor, entryManager, statusBarStateController,
-                    expansionStateLogger, mNotificationPanelLoggerFake);
+            super(
+                    notificationListener,
+                    uiBgExecutor,
+                    featureFlags,
+                    visibilityProvider,
+                    entryManager,
+                    notifPipeline,
+                    statusBarStateController,
+                    expansionStateLogger,
+                    mNotificationPanelLoggerFake
+            );
             mBarService = barService;
             // Make this on the current thread so we can wait for it during tests.
             mHandler = Handler.createAsync(Looper.myLooper());
         }
 
-        OnChildLocationsChangedListener
-                getChildLocationsChangedListenerForTest() {
+        OnChildLocationsChangedListener getChildLocationsChangedListenerForTest() {
             return mNotificationLocationsChangedListener;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index baed694..f26bb75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
@@ -97,6 +98,7 @@
 public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
     @Mock private NotificationGutsManager mNotificationGutsManager;
+    @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private TunerService mTunerService;
@@ -151,6 +153,7 @@
         mController = new NotificationStackScrollLayoutController(
                 true,
                 mNotificationGutsManager,
+                mVisibilityProvider,
                 mHeadsUpManager,
                 mNotificationRoundnessManager,
                 mTunerService,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index e4c4c63..75a8624 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -38,6 +38,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogcatEchoTracker;
@@ -50,6 +51,8 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -265,6 +268,8 @@
                 new PanelExpansionStateManager(),
                 mock(FeatureFlags.class),
                 mStatusBarIconController,
+                new StatusBarHideIconsForBouncerManager(
+                        mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()),
                 mKeyguardStateController,
                 mNetworkController,
                 mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index fbab075..f2be05b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -586,46 +586,50 @@
     }
 
     @Test
-    public void onTouchForwardedFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
+    public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
 
-        boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
-                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+        boolean returnVal = mNotificationPanelViewController
+                .getStatusBarTouchEventHandler()
+                .handleTouchEvent(
+                        MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
 
         assertThat(returnVal).isFalse();
         verify(mView, never()).dispatchTouchEvent(any());
     }
 
     @Test
-    public void onTouchForwardedFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
+    public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
         when(mView.isEnabled()).thenReturn(false);
 
-        boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
-                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+        boolean returnVal = mNotificationPanelViewController
+                .getStatusBarTouchEventHandler()
+                .handleTouchEvent(
+                        MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
 
         assertThat(returnVal).isTrue();
         verify(mView, never()).dispatchTouchEvent(any());
     }
 
     @Test
-    public void onTouchForwardedFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
+    public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
         when(mView.isEnabled()).thenReturn(false);
         MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
 
-        mTouchHandler.onTouchForwardedFromStatusBar(event);
+        mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
 
         verify(mView).dispatchTouchEvent(event);
     }
 
     @Test
-    public void onTouchForwardedFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
+    public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
         when(mView.isEnabled()).thenReturn(true);
         MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
 
-        mTouchHandler.onTouchForwardedFromStatusBar(event);
+        mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
 
         verify(mView).dispatchTouchEvent(event);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index eea8eb9..dc32007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -52,8 +52,6 @@
     @Mock
     private lateinit var panelView: ViewGroup
     @Mock
-    private lateinit var scrimController: ScrimController
-    @Mock
     private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
     @Mock
     private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
@@ -76,8 +74,6 @@
             val parent = FrameLayout(mContext) // add parent to keep layout params
             view = LayoutInflater.from(mContext)
                 .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
-            view.setScrimController(scrimController)
-            view.setBar(mock(StatusBar::class.java))
         }
 
         controller = createController(view)
@@ -85,10 +81,13 @@
 
     @Test
     fun constructor_setsTouchHandlerOnView() {
+        val interceptEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 10f, 10f, 0)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
 
+        view.onInterceptTouchEvent(interceptEvent)
         view.onTouchEvent(event)
 
+        assertThat(touchEventHandler.lastInterceptEvent).isEqualTo(interceptEvent)
         assertThat(touchEventHandler.lastEvent).isEqualTo(event)
     }
 
@@ -128,6 +127,11 @@
 
     private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
         var lastEvent: MotionEvent? = null
+        var lastInterceptEvent: MotionEvent? = null
+
+        override fun onInterceptTouchEvent(event: MotionEvent?) {
+            lastInterceptEvent = event
+        }
         override fun handleTouchEvent(event: MotionEvent?): Boolean {
             lastEvent = event
             return false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index e8ad5fd3..8d686ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -34,10 +34,6 @@
     private lateinit var panelViewController: PanelViewController
     @Mock
     private lateinit var panelView: ViewGroup
-    @Mock
-    private lateinit var scrimController: ScrimController
-    @Mock
-    private lateinit var statusBar: StatusBar
 
     private lateinit var view: PhoneStatusBarView
 
@@ -49,8 +45,6 @@
         `when`(panelViewController.view).thenReturn(panelView)
 
         view = PhoneStatusBarView(mContext, null)
-        view.setScrimController(scrimController)
-        view.setBar(statusBar)
     }
 
     @Test
@@ -65,12 +59,23 @@
     }
 
     @Test
+    fun onInterceptTouchEvent_listenerNotified() {
+        val handler = TestTouchEventHandler()
+        view.setTouchEventHandler(handler)
+
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        view.onInterceptTouchEvent(event)
+
+        assertThat(handler.lastInterceptEvent).isEqualTo(event)
+    }
+
+    @Test
     fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
         val handler = TestTouchEventHandler()
         view.setTouchEventHandler(handler)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
 
-        handler.returnValue = true
+        handler.handleTouchReturnValue = true
 
         assertThat(view.onTouchEvent(event)).isTrue()
     }
@@ -81,7 +86,7 @@
         view.setTouchEventHandler(handler)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
 
-        handler.returnValue = false
+        handler.handleTouchReturnValue = false
 
         assertThat(view.onTouchEvent(event)).isFalse()
     }
@@ -93,11 +98,17 @@
     }
 
     private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
+        var lastInterceptEvent: MotionEvent? = null
         var lastEvent: MotionEvent? = null
-        var returnValue: Boolean = false
+        var handleTouchReturnValue: Boolean = false
+
+        override fun onInterceptTouchEvent(event: MotionEvent?) {
+            lastInterceptEvent = event
+        }
+
         override fun handleTouchEvent(event: MotionEvent?): Boolean {
             lastEvent = event
-            return returnValue
+            return handleTouchReturnValue
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
index 8555306..0131293 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DisableFlagsLogger;
@@ -45,6 +46,8 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
 import org.junit.Before;
@@ -109,6 +112,8 @@
                 mStatusBarStateController,
                 mNotificationShadeWindowView,
                 mNotificationStackScrollLayoutController,
+                new StatusBarHideIconsForBouncerManager(
+                        mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()),
                 mPowerManager,
                 mVibratorHelper,
                 Optional.of(mVibrator),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 72a3d66..7791fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
@@ -68,6 +69,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -123,6 +125,8 @@
     private FeatureFlags mFeatureFlags;
     @Mock
     private NotifPipeline mNotifPipeline;
+    @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
 
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
@@ -179,6 +183,11 @@
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         when(mOnUserInteractionCallback.getGroupSummaryToDismiss(mNotificationRow.getEntry()))
                 .thenReturn(null);
+        when(mVisibilityProvider.obtain(anyString(), anyBoolean())).thenAnswer(
+                invocation-> NotificationVisibility.obtain(invocation.getArgument(0), 0, 1, false));
+        when(mVisibilityProvider.obtain(any(NotificationEntry.class), anyBoolean())).thenAnswer(
+                invocation-> NotificationVisibility.obtain(
+                        invocation.<NotificationEntry>getArgument(0).getKey(), 0, 1, false));
 
         HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
         NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
@@ -195,6 +204,7 @@
                         mUiBgExecutor,
                         mEntryManager,
                         mNotifPipeline,
+                        mVisibilityProvider,
                         headsUpManager,
                         mActivityStarter,
                         mClickNotifier,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 7a93d03..371b91f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -117,10 +117,12 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -264,6 +266,7 @@
     @Mock private StatusBarIconController mIconController;
     @Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
     @Mock private FeatureFlags mFeatureFlags;
+    @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private WallpaperManager mWallpaperManager;
     @Mock private IWallpaperManager mIWallpaperManager;
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -279,6 +282,7 @@
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
     private FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
     private InitController mInitController = new InitController();
+    private final DumpManager mDumpManager = new DumpManager();
 
     @Before
     public void setup() throws Exception {
@@ -301,9 +305,17 @@
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
 
         mMetricsLogger = new FakeMetricsLogger();
-        NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener,
-                mUiBgExecutor, mock(NotificationEntryManager.class), mStatusBarStateController,
-                mExpansionStateLogger, new NotificationPanelLoggerFake());
+        NotificationLogger notificationLogger = new NotificationLogger(
+                mNotificationListener,
+                mUiBgExecutor,
+                mFeatureFlags,
+                mVisibilityProvider,
+                mock(NotificationEntryManager.class),
+                mock(NotifPipeline.class),
+                mStatusBarStateController,
+                mExpansionStateLogger,
+                new NotificationPanelLoggerFake()
+        );
         notificationLogger.setVisibilityReporter(mock(Runnable.class));
 
         when(mCommandQueue.asBinder()).thenReturn(new Binder());
@@ -332,7 +344,7 @@
         }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
 
         WakefulnessLifecycle wakefulnessLifecycle =
-                new WakefulnessLifecycle(mContext, mIWallpaperManager, mock(DumpManager.class));
+                new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager);
         wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
         wakefulnessLifecycle.dispatchFinishedWakingUp();
 
@@ -390,7 +402,7 @@
                 mNetworkController,
                 mBatteryController,
                 mColorExtractor,
-                new ScreenLifecycle(mock(DumpManager.class)),
+                new ScreenLifecycle(mDumpManager),
                 wakefulnessLifecycle,
                 mStatusBarStateController,
                 Optional.of(mBubblesManager),
@@ -440,6 +452,7 @@
                 mAnimationScheduler,
                 mLocationPublisher,
                 mIconController,
+                new StatusBarHideIconsForBouncerManager(mCommandQueue, mMainExecutor, mDumpManager),
                 mLockscreenTransitionController,
                 mFeatureFlags,
                 mKeyguardUnlockAnimationController,
@@ -450,7 +463,7 @@
                 mUnlockedScreenOffAnimationController,
                 Optional.of(mStartingSurface),
                 mTunerService,
-                mock(DumpManager.class),
+                mDumpManager,
                 mActivityLaunchAnimator);
         when(mKeyguardViewMediator.registerStatusBar(
                 any(StatusBar.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 483dc9f..eb54fe0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -67,6 +67,27 @@
 inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
 
 /**
+ * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
+ * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
+ *
+ *     java.lang.NullPointerException: capture() must not be null
+ */
+class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
+    private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
+    fun capture(): T = wrapped.capture()
+    val value: T
+        get() = wrapped.value
+}
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
+        KotlinArgumentCaptor(T::class.java)
+
+/**
  * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
  *
  *    val captor = argumentCaptor<Foo>()
@@ -76,6 +97,8 @@
  * becomes:
  *
  *    val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ *
+ * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
  */
-inline fun <reified T : Any> withArgCaptor(block: ArgumentCaptor<T>.() -> Unit): T =
-        argumentCaptor<T>().apply { block() }.value
\ No newline at end of file
+inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
+        kotlinArgumentCaptor<T>().apply { block() }.value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index 8ea9da6..33ef9cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -18,8 +18,9 @@
 import android.testing.LeakCheck;
 
 import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.policy.DataSaverController;
 
 public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 15a92dc..c900ad5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -228,6 +229,8 @@
     @Mock
     private IStatusBarService mStatusBarService;
     @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
     private LauncherApps mLauncherApps;
     @Mock
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -354,6 +357,7 @@
                 mConfigurationController,
                 mStatusBarService,
                 mock(INotificationManager.class),
+                mVisibilityProvider,
                 interruptionStateProvider,
                 mZenModeController,
                 mLockscreenUserManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 43b181e..5ab2113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -78,6 +78,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -204,6 +205,8 @@
     @Mock
     private IStatusBarService mStatusBarService;
     @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
     private LauncherApps mLauncherApps;
     @Mock
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -319,6 +322,7 @@
                 mConfigurationController,
                 mStatusBarService,
                 mock(INotificationManager.class),
+                mVisibilityProvider,
                 interruptionStateProvider,
                 mZenModeController,
                 mLockscreenUserManager,
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 0146aa8..728efa5 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import static com.android.server.health.Utils.copy;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -26,13 +27,7 @@
 import android.content.Intent;
 import android.database.ContentObserver;
 import android.hardware.health.V1_0.HealthInfo;
-import android.hardware.health.V2_0.IHealth;
-import android.hardware.health.V2_0.Result;
 import android.hardware.health.V2_1.BatteryCapacityLevel;
-import android.hardware.health.V2_1.Constants;
-import android.hardware.health.V2_1.IHealthInfoCallback;
-import android.hidl.manager.V1_0.IServiceManager;
-import android.hidl.manager.V1_0.IServiceNotification;
 import android.metrics.LogMaker;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
@@ -44,7 +39,6 @@
 import android.os.DropBoxManager;
 import android.os.FileUtils;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
 import android.os.OsProtoEnums;
@@ -62,15 +56,14 @@
 import android.service.battery.BatteryServiceDumpProto;
 import android.sysprop.PowerProperties;
 import android.util.EventLog;
-import android.util.MutableInt;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.DumpUtils;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.health.HealthServiceWrapper;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 
@@ -82,8 +75,6 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * <p>BatteryService monitors the charging status, and charge level of the device
@@ -191,7 +182,6 @@
     private ActivityManagerInternal mActivityManagerInternal;
 
     private HealthServiceWrapper mHealthServiceWrapper;
-    private HealthHalCallback mHealthHalCallback;
     private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
     private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
     private long mLastBatteryLevelChangedSentMs;
@@ -274,13 +264,9 @@
 
     private void registerHealthCallback() {
         traceBegin("HealthInitWrapper");
-        mHealthServiceWrapper = new HealthServiceWrapper();
-        mHealthHalCallback = new HealthHalCallback();
         // IHealth is lazily retrieved.
         try {
-            mHealthServiceWrapper.init(mHealthHalCallback,
-                    new HealthServiceWrapper.IServiceManagerSupplier() {},
-                    new HealthServiceWrapper.IHealthSupplier() {});
+            mHealthServiceWrapper = HealthServiceWrapper.create(this::update);
         } catch (RemoteException ex) {
             Slog.e(TAG, "health: cannot register callback. (RemoteException)");
             throw ex.rethrowFromSystemServer();
@@ -454,25 +440,6 @@
         traceEnd();
     }
 
-    private static void copy(HealthInfo dst, HealthInfo src) {
-        dst.chargerAcOnline = src.chargerAcOnline;
-        dst.chargerUsbOnline = src.chargerUsbOnline;
-        dst.chargerWirelessOnline = src.chargerWirelessOnline;
-        dst.maxChargingCurrent = src.maxChargingCurrent;
-        dst.maxChargingVoltage = src.maxChargingVoltage;
-        dst.batteryStatus = src.batteryStatus;
-        dst.batteryHealth = src.batteryHealth;
-        dst.batteryPresent = src.batteryPresent;
-        dst.batteryLevel = src.batteryLevel;
-        dst.batteryVoltage = src.batteryVoltage;
-        dst.batteryTemperature = src.batteryTemperature;
-        dst.batteryCurrent = src.batteryCurrent;
-        dst.batteryCycleCount = src.batteryCycleCount;
-        dst.batteryFullCharge = src.batteryFullCharge;
-        dst.batteryChargeCounter = src.batteryChargeCounter;
-        dst.batteryTechnology = src.batteryTechnology;
-    }
-
     private static int plugType(HealthInfo healthInfo) {
         if (healthInfo.chargerAcOnline) {
             return BatteryManager.BATTERY_PLUGGED_AC;
@@ -1184,64 +1151,6 @@
         }
     }
 
-    private final class HealthHalCallback extends IHealthInfoCallback.Stub
-            implements HealthServiceWrapper.Callback {
-        @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
-            android.hardware.health.V2_1.HealthInfo propsLatest =
-                    new android.hardware.health.V2_1.HealthInfo();
-            propsLatest.legacy = props;
-
-            propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED;
-            propsLatest.batteryChargeTimeToFullNowSeconds =
-                Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED;
-
-            BatteryService.this.update(propsLatest);
-        }
-
-        @Override public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) {
-            BatteryService.this.update(props);
-        }
-
-        // on new service registered
-        @Override public void onRegistration(IHealth oldService, IHealth newService,
-                String instance) {
-            if (newService == null) return;
-
-            traceBegin("HealthUnregisterCallback");
-            try {
-                if (oldService != null) {
-                    int r = oldService.unregisterCallback(this);
-                    if (r != Result.SUCCESS) {
-                        Slog.w(TAG, "health: cannot unregister previous callback: " +
-                                Result.toString(r));
-                    }
-                }
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
-                            + ex.getMessage());
-            } finally {
-                traceEnd();
-            }
-
-            traceBegin("HealthRegisterCallback");
-            try {
-                int r = newService.registerCallback(this);
-                if (r != Result.SUCCESS) {
-                    Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
-                    return;
-                }
-                // registerCallback does NOT guarantee that update is called
-                // immediately, so request a manual update here.
-                newService.update();
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "health: cannot register callback (transaction error): "
-                        + ex.getMessage());
-            } finally {
-                traceEnd();
-            }
-        }
-    }
-
     private final class BinderService extends Binder {
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1265,71 +1174,11 @@
     private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
         @Override
         public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
-            traceBegin("HealthGetProperty");
-            try {
-                IHealth service = mHealthServiceWrapper.getLastService();
-                if (service == null) throw new RemoteException("no health service");
-                final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
-                switch(id) {
-                    case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
-                        service.getChargeCounter((int result, int value) -> {
-                            outResult.value = result;
-                            if (result == Result.SUCCESS) prop.setLong(value);
-                        });
-                        break;
-                    case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
-                        service.getCurrentNow((int result, int value) -> {
-                            outResult.value = result;
-                            if (result == Result.SUCCESS) prop.setLong(value);
-                        });
-                        break;
-                    case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
-                        service.getCurrentAverage((int result, int value) -> {
-                            outResult.value = result;
-                            if (result == Result.SUCCESS) prop.setLong(value);
-                        });
-                        break;
-                    case BatteryManager.BATTERY_PROPERTY_CAPACITY:
-                        service.getCapacity((int result, int value) -> {
-                            outResult.value = result;
-                            if (result == Result.SUCCESS) prop.setLong(value);
-                        });
-                        break;
-                    case BatteryManager.BATTERY_PROPERTY_STATUS:
-                        service.getChargeStatus((int result, int value) -> {
-                            outResult.value = result;
-                            if (result == Result.SUCCESS) prop.setLong(value);
-                        });
-                        break;
-                    case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
-                        service.getEnergyCounter((int result, long value) -> {
-                            outResult.value = result;
-                            if (result == Result.SUCCESS) prop.setLong(value);
-                        });
-                        break;
-                }
-                return outResult.value;
-            } finally {
-                traceEnd();
-            }
+            return mHealthServiceWrapper.getProperty(id, prop);
         }
         @Override
         public void scheduleUpdate() throws RemoteException {
-            mHealthServiceWrapper.getHandlerThread().getThreadHandler().post(() -> {
-                traceBegin("HealthScheduleUpdate");
-                try {
-                    IHealth service = mHealthServiceWrapper.getLastService();
-                    if (service == null) {
-                        Slog.e(TAG, "no health service");
-                        return;
-                    }
-                    service.update();
-                } catch (RemoteException ex) {
-                    Slog.e(TAG, "Cannot call update on health HAL", ex);
-                } finally {
-                    traceEnd();
-                }
-            });
+            mHealthServiceWrapper.scheduleUpdate();
         }
     }
 
@@ -1418,184 +1267,4 @@
             BatteryService.this.suspendBatteryInput();
         }
     }
-
-    /**
-     * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when
-     * necessary.
-     *
-     * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and
-     * the internal service is refreshed.
-     * On death of an existing IHealth service, the internal service is NOT cleared to avoid
-     * race condition between death notification and new service notification. Hence,
-     * a caller must check for transaction errors when calling into the service.
-     *
-     * @hide Should only be used internally.
-     */
-    public static final class HealthServiceWrapper {
-        private static final String TAG = "HealthServiceWrapper";
-        public static final String INSTANCE_VENDOR = "default";
-
-        private final IServiceNotification mNotification = new Notification();
-        private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder");
-        // These variables are fixed after init.
-        private Callback mCallback;
-        private IHealthSupplier mHealthSupplier;
-        private String mInstanceName;
-
-        // Last IHealth service received.
-        private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
-
-        /**
-         * init should be called after constructor. For testing purposes, init is not called by
-         * constructor.
-         */
-        public HealthServiceWrapper() {
-        }
-
-        public IHealth getLastService() {
-            return mLastService.get();
-        }
-
-        /**
-         * See {@link #init(Callback, IServiceManagerSupplier, IHealthSupplier)}
-         */
-        public void init() throws RemoteException, NoSuchElementException {
-            init(/* callback= */null, new HealthServiceWrapper.IServiceManagerSupplier() {},
-                    new HealthServiceWrapper.IHealthSupplier() {});
-        }
-
-        /**
-         * Start monitoring registration of new IHealth services. Only instance
-         * {@link #INSTANCE_VENDOR} and in device / framework manifest are used. This function should
-         * only be called once.
-         *
-         * mCallback.onRegistration() is called synchronously (aka in init thread) before
-         * this method returns if callback is not null.
-         *
-         * @throws RemoteException transaction error when talking to IServiceManager
-         * @throws NoSuchElementException if one of the following cases:
-         *         - No service manager;
-         *         - {@link #INSTANCE_VENDOR} is not in manifests (i.e. not
-         *           available on this device), or none of these instances are available to current
-         *           process.
-         * @throws NullPointerException when supplier is null
-         */
-        void init(@Nullable Callback callback,
-                  IServiceManagerSupplier managerSupplier,
-                  IHealthSupplier healthSupplier)
-                throws RemoteException, NoSuchElementException, NullPointerException {
-            if (managerSupplier == null || healthSupplier == null) {
-                throw new NullPointerException();
-            }
-            IServiceManager manager;
-
-            mHealthSupplier = healthSupplier;
-
-            // Initialize mLastService and call callback for the first time (in init thread)
-            IHealth newService = null;
-            traceBegin("HealthInitGetService_" + INSTANCE_VENDOR);
-            try {
-                newService = healthSupplier.get(INSTANCE_VENDOR);
-            } catch (NoSuchElementException ex) {
-                /* ignored, handled below */
-            } finally {
-                traceEnd();
-            }
-            if (newService != null) {
-                mInstanceName = INSTANCE_VENDOR;
-                mLastService.set(newService);
-            }
-
-            if (mInstanceName == null || newService == null) {
-                throw new NoSuchElementException(String.format(
-                        "IHealth service instance %s isn't available. Perhaps no permission?",
-                        INSTANCE_VENDOR));
-            }
-
-            if (callback != null) {
-                mCallback = callback;
-                mCallback.onRegistration(null, newService, mInstanceName);
-            }
-
-            // Register for future service registrations
-            traceBegin("HealthInitRegisterNotification");
-            mHandlerThread.start();
-            try {
-                managerSupplier.get().registerForNotifications(
-                        IHealth.kInterfaceName, mInstanceName, mNotification);
-            } finally {
-                traceEnd();
-            }
-            Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
-        }
-
-        @VisibleForTesting
-        HandlerThread getHandlerThread() {
-            return mHandlerThread;
-        }
-
-        interface Callback {
-            /**
-             * This function is invoked asynchronously when a new and related IServiceNotification
-             * is received.
-             * @param service the recently retrieved service from IServiceManager.
-             * Can be a dead service before service notification of a new service is delivered.
-             * Implementation must handle cases for {@link RemoteException}s when calling
-             * into service.
-             * @param instance instance name.
-             */
-            void onRegistration(IHealth oldService, IHealth newService, String instance);
-        }
-
-        /**
-         * Supplier of services.
-         * Must not return null; throw {@link NoSuchElementException} if a service is not available.
-         */
-        interface IServiceManagerSupplier {
-            default IServiceManager get() throws NoSuchElementException, RemoteException {
-                return IServiceManager.getService();
-            }
-        }
-        /**
-         * Supplier of services.
-         * Must not return null; throw {@link NoSuchElementException} if a service is not available.
-         */
-        interface IHealthSupplier {
-            default IHealth get(String name) throws NoSuchElementException, RemoteException {
-                return IHealth.getService(name, true /* retry */);
-            }
-        }
-
-        private class Notification extends IServiceNotification.Stub {
-            @Override
-            public final void onRegistration(String interfaceName, String instanceName,
-                    boolean preexisting) {
-                if (!IHealth.kInterfaceName.equals(interfaceName)) return;
-                if (!mInstanceName.equals(instanceName)) return;
-
-                // This runnable only runs on mHandlerThread and ordering is ensured, hence
-                // no locking is needed inside the runnable.
-                mHandlerThread.getThreadHandler().post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            IHealth newService = mHealthSupplier.get(mInstanceName);
-                            IHealth oldService = mLastService.getAndSet(newService);
-
-                            // preexisting may be inaccurate (race). Check for equality here.
-                            if (Objects.equals(newService, oldService)) return;
-
-                            Slog.i(TAG, "health: new instance registered " + mInstanceName);
-                            // #init() may be called with null callback. Skip null callbacks.
-                            if (mCallback == null) return;
-                            mCallback.onRegistration(oldService, newService, mInstanceName);
-                        } catch (NoSuchElementException | RemoteException ex) {
-                            Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
-                                    + "': " + ex.getMessage() + ". Perhaps no permission?");
-                        }
-                    }
-                });
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b5623be..bf744cf 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -90,6 +90,7 @@
 import android.app.ForegroundServiceDidNotStartInTimeException;
 import android.app.ForegroundServiceStartNotAllowedException;
 import android.app.IApplicationThread;
+import android.app.IForegroundServiceObserver;
 import android.app.IServiceConnection;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -128,6 +129,7 @@
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.Process;
 import android.os.RemoteCallback;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -267,6 +269,12 @@
     final SparseLongArray mFgsDeferralEligible = new SparseLongArray();
 
     /**
+     * Foreground service observers: track what apps have FGSes
+     */
+    final RemoteCallbackList<IForegroundServiceObserver> mFgsObservers =
+            new RemoteCallbackList<>();
+
+    /**
      * Map of services that are asked to be brought up (start/binding) but not ready to.
      */
     private ArrayMap<ServiceRecord, ArrayList<Runnable>> mPendingBringups = new ArrayMap<>();
@@ -1382,6 +1390,10 @@
         return false;
     }
 
+    /**
+     * Put the named service into the foreground mode
+     */
+    @GuardedBy("mAm")
     public void setServiceForegroundLocked(ComponentName className, IBinder token,
             int id, Notification notification, int flags, int foregroundServiceType) {
         final int userId = UserHandle.getCallingUserId();
@@ -1746,6 +1758,7 @@
     /**
      * @param id Notification ID.  Zero === exit foreground state for the given service.
      */
+    @GuardedBy("mAm")
     private void setServiceForegroundInnerLocked(final ServiceRecord r, int id,
             Notification notification, int flags, int foregroundServiceType) {
         if (id != 0) {
@@ -1981,6 +1994,7 @@
                     }
                     // Even if the service is already a FGS, we need to update the notification,
                     // so we need to call it again.
+                    signalForegroundServiceObserversLocked(r);
                     r.postNotification();
                     if (r.app != null) {
                         updateServiceForegroundLocked(psr, true);
@@ -2060,6 +2074,7 @@
                         r.mFgsExitTime > r.mFgsEnterTime
                                 ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0);
                 r.mFgsNotificationWasDeferred = false;
+                signalForegroundServiceObserversLocked(r);
                 resetFgsRestrictionLocked(r);
                 mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
                 if (r.app != null) {
@@ -4552,7 +4567,8 @@
         }
 
         cancelForegroundNotificationLocked(r);
-        if (r.isForeground) {
+        final boolean exitingFg = r.isForeground;
+        if (exitingFg) {
             decActiveForegroundAppLocked(smap, r);
             synchronized (mAm.mProcessStats.mLock) {
                 ServiceState stracker = r.getTracker();
@@ -4578,6 +4594,11 @@
         r.foregroundId = 0;
         r.foregroundNoti = null;
         resetFgsRestrictionLocked(r);
+        // Signal FGS observers *after* changing the isForeground state, and
+        // only if this was an actual state change.
+        if (exitingFg) {
+            signalForegroundServiceObserversLocked(r);
+        }
 
         // Clear start entries.
         r.clearDeliveredStartsLocked();
@@ -5133,6 +5154,54 @@
         }
     }
 
+    @GuardedBy("mAm")
+    private void signalForegroundServiceObserversLocked(ServiceRecord r) {
+        final int num = mFgsObservers.beginBroadcast();
+        for (int i = 0; i < num; i++) {
+            try {
+                mFgsObservers.getBroadcastItem(i).onForegroundStateChanged(r,
+                        r.appInfo.packageName, r.userId, r.isForeground);
+            } catch (RemoteException e) {
+                // Will be unregistered automatically by RemoteCallbackList's dead-object
+                // tracking, so nothing we need to do here.
+            }
+        }
+        mFgsObservers.finishBroadcast();
+    }
+
+    @GuardedBy("mAm")
+    boolean registerForegroundServiceObserverLocked(final int callingUid,
+            IForegroundServiceObserver callback) {
+        // We always tell the newly-registered observer about any current FGSes.  The
+        // most common case for this is a SysUI crash & relaunch; it needs to
+        // reconstruct its tracking of stoppable-FGS-hosting apps.
+        try {
+            final int mapSize = mServiceMap.size();
+            for (int mapIndex = 0; mapIndex < mapSize; mapIndex++) {
+                final ServiceMap smap = mServiceMap.valueAt(mapIndex);
+                if (smap != null) {
+                    final int numServices = smap.mServicesByInstanceName.size();
+                    for (int i = 0; i < numServices; i++) {
+                        final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+                        if (sr.isForeground && callingUid == sr.appInfo.uid) {
+                            callback.onForegroundStateChanged(sr, sr.appInfo.packageName,
+                                    sr.userId, true);
+                        }
+                    }
+                }
+            }
+            // Callback is fine, go ahead and record it
+            mFgsObservers.register(callback);
+        } catch (RemoteException e) {
+            // Whoops, something wrong with the callback.  Don't register it, and
+            // report error back to the caller.
+            Slog.e(TAG_SERVICE, "Bad FGS observer from uid " + callingUid);
+            return false;
+        }
+
+        return true;
+    }
+
     void forceStopPackageLocked(String packageName, int userId) {
         ServiceMap smap = mServiceMap.get(userId);
         if (smap != null && smap.mActiveForegroundApps.size() > 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b1a1e0d..0eed190 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.FILTER_EVENTS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
@@ -31,7 +32,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
-import static android.app.ActivityManager.StopBgUsersOnSwitch;
+import static android.app.ActivityManager.StopUserOnSwitch;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.AppOpsManager.OP_NONE;
@@ -168,6 +169,7 @@
 import android.app.IActivityController;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
+import android.app.IForegroundServiceObserver;
 import android.app.IInstrumentationWatcher;
 import android.app.INotificationManager;
 import android.app.IProcessObserver;
@@ -442,6 +444,14 @@
     private static final String SYSTEM_PROPERTY_DEVICE_PROVISIONED =
             "persist.sys.device_provisioned";
 
+    /**
+     * Enabling this flag enforces the requirement for context registered receivers to use one of
+     * {@link Context#RECEIVER_EXPORTED} or {@link Context#RECEIVER_NOT_EXPORTED} for unprotected
+     * broadcasts
+     */
+    private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT =
+            SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false);
+
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
     private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
@@ -3754,12 +3764,12 @@
 
     @Override
     public void makeServicesNonForeground(final String packageName, int userId) {
-        if (checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+        if (checkCallingPermission(MANAGE_ACTIVITY_TASKS)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: makeServicesNonForeground() from pid="
                     + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+                    + " requires " + MANAGE_ACTIVITY_TASKS;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
@@ -3776,6 +3786,27 @@
     }
 
     @Override
+    public boolean registerForegroundServiceObserver(IForegroundServiceObserver callback) {
+        final int callingUid = Binder.getCallingUid();
+        final int permActivityTasks = checkCallingPermission(MANAGE_ACTIVITY_TASKS);
+        final int permAcrossUsersFull = checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
+        if (permActivityTasks != PackageManager.PERMISSION_GRANTED
+                || permAcrossUsersFull != PERMISSION_GRANTED) {
+            String msg = "Permission Denial: registerForegroundServiceObserver() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + callingUid
+                    + " requires " + MANAGE_ACTIVITY_TASKS
+                    + " and " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        synchronized (this) {
+            return mServices.registerForegroundServiceObserverLocked(callingUid, callback);
+        }
+    }
+
+    @Override
     public void forceStopPackage(final String packageName, int userId) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -12623,28 +12654,43 @@
             // an error so the consumer can know to explicitly set the value for their flag.
             // If the caller is registering for a sticky broadcast with a null receiver, we won't
             // require a flag
-            if (!onlyProtectedBroadcasts && receiver != null && (
-                    CompatChanges.isChangeEnabled(
-                            DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
-                            && (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED))
-                            == 0)) {
-                Slog.e(TAG,
-                        callerPackage + ": Targeting T+ (version " + Build.VERSION_CODES.TIRAMISU
-                                + " and above) requires that one of RECEIVER_EXPORTED or "
-                                + "RECEIVER_NOT_EXPORTED be specified when registering a receiver");
-            } else if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
+            final boolean explicitExportStateDefined =
+                    (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
+            if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
                     (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
                 throw new IllegalArgumentException(
                         "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
                                 + "flag");
             }
+            if (CompatChanges.isChangeEnabled(DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED,
+                    callingUid)
+                    && !explicitExportStateDefined) {
+                if (ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT) {
+                    throw new SecurityException(
+                            callerPackage + ": Targeting T+ (version "
+                                    + Build.VERSION_CODES.TIRAMISU
+                                    + " and above) requires that one of RECEIVER_EXPORTED or "
+                                    + "RECEIVER_NOT_EXPORTED be specified when registering a "
+                                    + "receiver");
+                } else {
+                    Slog.wtf(TAG,
+                            callerPackage + ": Targeting T+ (version "
+                                    + Build.VERSION_CODES.TIRAMISU
+                                    + " and above) requires that one of RECEIVER_EXPORTED or "
+                                    + "RECEIVER_NOT_EXPORTED be specified when registering a "
+                                    + "receiver");
+                    // Assume default behavior-- flag check is not enforced
+                    flags |= Context.RECEIVER_EXPORTED;
+                }
+            } else if (!CompatChanges.isChangeEnabled(DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED,
+                    callingUid)) {
+                // Change is not enabled, thus not targeting T+. Assume exported.
+                flags |= Context.RECEIVER_EXPORTED;
+            }
         }
 
         // Dynamic receivers are exported by default for versions prior to T
-        final boolean exported =
-                ((flags & Context.RECEIVER_EXPORTED) != 0
-                        || (!CompatChanges.isChangeEnabled(
-                        DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)));
+        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
 
         ArrayList<Intent> allSticky = null;
         if (stickyIntents != null) {
@@ -15333,8 +15379,8 @@
     }
 
     @Override
-    public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) {
-        mUserController.setStopBackgroundUsersOnSwitch(value);
+    public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
+        mUserController.setStopUserOnSwitch(value);
     }
 
     @Override
@@ -16662,8 +16708,8 @@
         }
 
         @Override
-        public void setStopBackgroundUsersOnSwitch(int value) {
-            ActivityManagerService.this.setStopBackgroundUsersOnSwitch(value);
+        public void setStopUserOnSwitch(int value) {
+            ActivityManagerService.this.setStopUserOnSwitch(value);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 25adddd..b144c8d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -333,7 +333,7 @@
                 case "get-isolated-pids":
                     return runGetIsolatedProcesses(pw);
                 case "set-stop-user-on-switch":
-                    return runSetStopBackgroundUsersOnSwitch(pw);
+                    return runSetStopUserOnSwitch(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -3184,25 +3184,24 @@
         return 0;
     }
 
-    private int runSetStopBackgroundUsersOnSwitch(PrintWriter pw) throws RemoteException {
+    private int runSetStopUserOnSwitch(PrintWriter pw) throws RemoteException {
         mInternal.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                "setStopBackgroundUsersOnSwitch()");
+                "setStopUserOnSwitch()");
         String arg = getNextArg();
         if (arg == null) {
-            Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): resetting to default value");
-            mInternal.setStopBackgroundUsersOnSwitch(
-                    ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT);
+            Slogf.i(TAG, "setStopUserOnSwitch(): resetting to default value");
+            mInternal.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT);
             pw.println("Reset to default value");
             return 0;
         }
 
         boolean stop = Boolean.parseBoolean(arg);
         int value = stop
-                ? ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE
-                : ActivityManager.STOP_BG_USERS_ON_SWITCH_FALSE;
+                ? ActivityManager.STOP_USER_ON_SWITCH_TRUE
+                : ActivityManager.STOP_USER_ON_SWITCH_FALSE;
 
-        Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): setting to %d (%b)", value, stop);
-        mInternal.setStopBackgroundUsersOnSwitch(value);
+        Slogf.i(TAG, "runSetStopUserOnSwitch(): setting to %d (%b)", value, stop);
+        mInternal.setStopUserOnSwitch(value);
         pw.println("Set to " + stop);
 
         return 0;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 8638c7d..fd6f099 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -792,7 +792,7 @@
                     + " due to receiver " + filter.receiverList.app
                     + " (uid " + filter.receiverList.uid + ")"
                     + " not specifying RECEIVER_EXPORTED");
-            // skip = true;
+            skip = true;
         }
 
         if (skip) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0c518a0..6dbdead 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -19,9 +19,9 @@
 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT;
-import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE;
-import static android.app.ActivityManager.StopBgUsersOnSwitch;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
+import static android.app.ActivityManager.StopUserOnSwitch;
 import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
@@ -376,7 +376,7 @@
      * user is switched.
      */
     @GuardedBy("mLock")
-    private @StopBgUsersOnSwitch int mStopBgUsersOnSwitch = STOP_BG_USERS_ON_SWITCH_DEFAULT;
+    private @StopUserOnSwitch int mStopUserOnSwitch = STOP_USER_ON_SWITCH_DEFAULT;
 
     UserController(ActivityManagerService service) {
         this(new Injector(service));
@@ -418,29 +418,27 @@
         }
     }
 
-    void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) {
+    void setStopUserOnSwitch(@StopUserOnSwitch int value) {
         if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
                 == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                 == PackageManager.PERMISSION_DENIED) {
             throw new SecurityException(
                     "You either need MANAGE_USERS or INTERACT_ACROSS_USERS_FULL permission to "
-                            + "call setStopBackgroundUsersOnSwitch()");
+                            + "call setStopUserOnSwitch()");
         }
 
         synchronized (mLock) {
-            Slogf.i(TAG, "setStopBackgroundUsersOnSwitch(): %d -> %d",
-                    mStopBgUsersOnSwitch, value);
-            mStopBgUsersOnSwitch = value;
+            Slogf.i(TAG, "setStopUserOnSwitch(): %d -> %d", mStopUserOnSwitch, value);
+            mStopUserOnSwitch = value;
         }
     }
 
-    private boolean shouldStopBackgroundUsersOnSwitch() {
+    private boolean shouldStopUserOnSwitch() {
         synchronized (mLock) {
-            if (mStopBgUsersOnSwitch != STOP_BG_USERS_ON_SWITCH_DEFAULT) {
-                final boolean value = mStopBgUsersOnSwitch == STOP_BG_USERS_ON_SWITCH_TRUE;
-                Slogf.i(TAG, "isStopBackgroundUsersOnSwitch(): returning overridden value (%b)",
-                        value);
+            if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) {
+                final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE;
+                Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value);
                 return value;
             }
         }
@@ -1846,7 +1844,7 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) {
+    private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) {
         // Never stop system user
         if (oldUserId == UserHandle.USER_SYSTEM) {
             return;
@@ -1854,18 +1852,17 @@
         boolean hasRestriction =
                 hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
         synchronized (mLock) {
-            // If running in background is disabled or mStopBackgroundUsersOnSwitch mode,
-            // stop the user.
-            boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch();
+            // If running in background is disabled or mStopUserOnSwitch mode, stop the user.
+            boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch();
             if (!disallowRunInBg) {
                 if (DEBUG_MU) {
-                    Slogf.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related "
-                            + "users", oldUserId);
+                    Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users",
+                            oldUserId);
                 }
                 return;
             }
             if (DEBUG_MU) {
-                Slogf.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users",
+                Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users",
                         oldUserId);
             }
             stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
@@ -1979,7 +1976,7 @@
         mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
         stopGuestOrEphemeralUserIfBackground(oldUserId);
-        stopBackgroundUsersOnSwitchIfEnforced(oldUserId);
+        stopUserOnSwitchIfEnforced(oldUserId);
 
         t.traceEnd(); // end continueUserSwitch
     }
@@ -2671,9 +2668,8 @@
             pw.println("  mTargetUserId:" + mTargetUserId);
             pw.println("  mLastActiveUsers:" + mLastActiveUsers);
             pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
-            pw.println("  shouldStopBackgroundUsersOnSwitch():"
-                    + shouldStopBackgroundUsersOnSwitch());
-            pw.println("  mStopBgUsersOnSwitch:" + mStopBgUsersOnSwitch);
+            pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
+            pw.println("  mStopUserOnSwitch:" + mStopUserOnSwitch);
             pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
             pw.println("  mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
             pw.println("  mInitialized:" + mInitialized);
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 9d2cff9..5ce72c2 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -108,8 +108,9 @@
 
     /**
      * When enabled this change id forces the packages it is applied to override the default
-     * camera rotate & crop behavior. The default behavior along with all possible override
-     * combinations is discussed in the table below.
+     * camera rotate & crop behavior and always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE .
+     * The default behavior along with all possible override combinations is discussed in the table
+     * below.
      */
     @ChangeId
     @Overridable
@@ -121,9 +122,7 @@
      * When enabled this change id forces the packages it is applied to ignore the current value of
      * 'android:resizeableActivity' as well as target SDK equal to or below M and consider the
      * activity as non-resizeable. In this case, the value of camera rotate & crop will only depend
-     * on potential mismatches between the orientation of the camera and the fixed orientation of
-     * the activity. You can check the table below for further details on the possible override
-     * combinations.
+     * on the needed compensation considering the current display rotation.
      */
     @ChangeId
     @Overridable
@@ -132,67 +131,30 @@
     public static final long OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK = 191513214L; // buganizer id
 
     /**
-     * This change id forces the packages it is applied to override the default camera rotate & crop
-     * behavior. Enabling it will set the crop & rotate parameter to
-     * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_90} and disabling it
-     * will reset the parameter to
-     * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_NONE} as long as camera
-     * clients include {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_AUTO}
-     * in their capture requests.
-     *
-     * This treatment only takes effect if OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS is also enabled.
-     * The table below includes further information about the possible override combinations.
-     */
-    @ChangeId
-    @Overridable
-    @Disabled
-    @TestApi
-    public static final long OVERRIDE_CAMERA_ROTATE_AND_CROP = 190069291L; //buganizer id
-
-    /**
      * Possible override combinations
      *
-     *            |OVERRIDE     |          |OVERRIDE_
-     *            |CAMERA_      |OVERRIDE  |CAMERA_
-     *            |ROTATE_      |CAMERA_   |RESIZEABLE_
-     *            |AND_CROP_    |ROTATE_   |AND_SDK_
-     *            |DEFAULTS     |AND_CROP  |CHECK
-     * ______________________________________________
-     * Default    |             |          |
-     * Behavior   | D           |D         |D
-     * ______________________________________________
-     * Ignore     |             |          |
-     * SDK&Resize | D           |D         |E
-     * ______________________________________________
-     * Default    |             |          |
-     * Behavior   | D           |E         |D
-     * ______________________________________________
-     * Ignore     |             |          |
-     * SDK&Resize | D           |E         |E
-     * ______________________________________________
-     * Rotate&Crop|             |          |
-     * disabled   | E           |D         |D
-     * ______________________________________________
-     * Rotate&Crop|             |          |
-     * disabled   | E           |D         |E
-     * ______________________________________________
-     * Rotate&Crop|             |          |
-     * enabled    | E           |E         |D
-     * ______________________________________________
-     * Rotate&Crop|             |          |
-     * enabled    | E           |E         |E
-     * ______________________________________________
+     *                             |OVERRIDE     |OVERRIDE_
+     *                             |CAMERA_      |CAMERA_
+     *                             |ROTATE_      |RESIZEABLE_
+     *                             |AND_CROP_    |AND_SDK_
+     *                             |DEFAULTS     |CHECK
+     * _________________________________________________
+     * Default Behavior            | D           |D
+     * _________________________________________________
+     * Ignore SDK&Resize           | D           |E
+     * _________________________________________________
+     * SCALER_ROTATE_AND_CROP_NONE | E           |D, E
+     * _________________________________________________
      * Where:
-     * E -> Override enabled
-     * D -> Override disabled
-     * Default behavior ->  Rotate&crop will be enabled only in cases
-     *                      where the fixed app orientation mismatches
-     *                      with the orientation of the camera.
-     *                      Additionally the app must either target M (or below)
-     *                      or is declared as non-resizeable.
-     * Ignore SDK&Resize -> Rotate&crop will be enabled only in cases
-     *                      where the fixed app orientation mismatches
-     *                      with the orientation of the camera.
+     * E                            -> Override enabled
+     * D                            -> Override disabled
+     * Default behavior             -> Rotate&crop will be calculated depending on the required
+     *                                 compensation necessary for the current display rotation.
+     *                                 Additionally the app must either target M (or below)
+     *                                 or is declared as non-resizeable.
+     * Ignore SDK&Resize            -> The Rotate&crop value will depend on the required
+     *                                 compensation for the current display rotation.
+     * SCALER_ROTATE_AND_CROP_NONE  -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE
      */
 
     // Flags arguments to NFC adapter to enable/disable NFC
@@ -543,14 +505,8 @@
             if ((taskInfo != null) && (CompatChanges.isChangeEnabled(
                         OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName,
                         UserHandle.getUserHandleForUid(taskInfo.userId)))) {
-                if (CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_ROTATE_AND_CROP, packageName,
-                        UserHandle.getUserHandleForUid(taskInfo.userId))) {
-                    Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP enabled!");
+                    Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS enabled!");
                     return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
-                } else {
-                    Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP disabled!");
-                    return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
-                }
             }
             boolean ignoreResizableAndSdkCheck = false;
             if ((taskInfo != null) && (CompatChanges.isChangeEnabled(
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
index 11dc1db..e8762a3 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
@@ -16,19 +16,24 @@
 
 package com.android.server.compat.overrides;
 
+import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 
 import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 
+import android.annotation.Nullable;
 import android.app.compat.PackageOverride;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.KeyValueListParser;
+import android.util.Pair;
 import android.util.Slog;
 
+import libcore.util.HexEncoding;
+
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
@@ -178,7 +183,9 @@
      * overrides, and returns a map from change ID to {@link PackageOverride} instances to add.
      *
      * <p>Each change override is in the following format:
-     * '<change-id>:<min-version-code?>:<max-version-code?>:<enabled>'.
+     * '<signature?>~<change-id>:<min-version-code?>:<max-version-code?>:<enabled>'.
+     *
+     * <p>The signature is optional, and will only be enforced if included.
      *
      * <p>If there are multiple overrides that should be added with the same change ID, the one
      * that best fits the given {@code versionCode} is added.
@@ -187,14 +194,27 @@
      *
      * <p>If a change override entry in {@code configStr} is invalid, it will be ignored.
      */
-    static Map<Long, PackageOverride> parsePackageOverrides(String configStr, long versionCode,
+    Map<Long, PackageOverride> parsePackageOverrides(String configStr, String packageName,
+            long versionCode,
             Set<Long> changeIdsToSkip) {
         if (configStr.isEmpty()) {
             return emptyMap();
         }
         PackageOverrideComparator comparator = new PackageOverrideComparator(versionCode);
         Map<Long, PackageOverride> overridesToAdd = new ArrayMap<>();
-        for (String overrideEntryString : configStr.split(",")) {
+
+        Pair<String, String> signatureAndConfig = extractSignatureFromConfig(configStr);
+        if (signatureAndConfig == null) {
+            return emptyMap();
+        }
+        final String signature = signatureAndConfig.first;
+        final String overridesConfig = signatureAndConfig.second;
+
+        if (!verifySignature(packageName, signature)) {
+            return emptyMap();
+        }
+
+        for (String overrideEntryString : overridesConfig.split(",")) {
             List<String> changeIdAndVersions = Arrays.asList(overrideEntryString.split(":", 4));
             if (changeIdAndVersions.size() != 4) {
                 Slog.w(TAG, "Invalid change override entry: " + overrideEntryString);
@@ -252,6 +272,51 @@
     }
 
     /**
+     * Extracts the signature from the config string if one exists.
+     *
+     * @param configStr String in the form of <signature?>~<overrideConfig>
+     */
+    @Nullable
+    private static Pair<String, String> extractSignatureFromConfig(String configStr) {
+        final List<String> signatureAndConfig = Arrays.asList(configStr.split("~"));
+
+        if (signatureAndConfig.size() == 1) {
+            // The config string doesn't contain a signature.
+            return Pair.create("", configStr);
+        }
+
+        if (signatureAndConfig.size() > 2) {
+            Slog.w(TAG, "Only one signature per config is supported. Config: " + configStr);
+            return null;
+        }
+
+        return Pair.create(signatureAndConfig.get(0), signatureAndConfig.get(1));
+    }
+
+    /**
+     * Verifies that the specified package was signed with a particular signature.
+     *
+     * @param packageName The package to check.
+     * @param signature   The optional signature to verify. If empty, we return true.
+     * @return Whether the package is signed with that signature.
+     */
+    private boolean verifySignature(String packageName, String signature) {
+        try {
+            final boolean signatureValid = signature.isEmpty()
+                    || mPackageManager.hasSigningCertificate(packageName,
+                    HexEncoding.decode(signature), CERT_INPUT_SHA256);
+
+            if (!signatureValid) {
+                Slog.w(TAG, packageName + " did not have expected signature: " + signature);
+            }
+            return signatureValid;
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Unable to verify signature " + signature + " for " + packageName, e);
+            return false;
+        }
+    }
+
+    /**
      * A {@link Comparator} that compares @link PackageOverride} instances with respect to a
      * specified {@code versionCode} as follows:
      *
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
index 6aed4b0..7e58b6c 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -196,8 +196,8 @@
     private void applyPackageOverrides(String configStr, String packageName, long versionCode,
             Set<Long> ownedChangeIds, Set<Long> changeIdsToSkip,
             boolean removeOtherOwnedOverrides) {
-        Map<Long, PackageOverride> overridesToAdd = AppCompatOverridesParser.parsePackageOverrides(
-                configStr, versionCode, changeIdsToSkip);
+        Map<Long, PackageOverride> overridesToAdd = mOverridesParser.parsePackageOverrides(
+                configStr, packageName, versionCode, changeIdsToSkip);
         putPackageOverrides(packageName, overridesToAdd);
 
         if (!removeOtherOwnedOverrides) {
@@ -426,5 +426,5 @@
                     break;
             }
         }
-    };
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 8481ad0..d35fc19 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3150,7 +3150,7 @@
         @Override // Binder call
         public void setUserPreferredDisplayMode(Display.Mode mode) {
             mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.WRITE_SECURE_SETTINGS,
+                    Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE,
                     "Permission required to set the user preferred display mode.");
             final long token = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/health/HealthHalCallbackHidl.java b/services/core/java/com/android/server/health/HealthHalCallbackHidl.java
new file mode 100644
index 0000000..6b4d7b7
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthHalCallbackHidl.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 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.health;
+
+import android.annotation.NonNull;
+import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
+import android.hardware.health.V2_1.BatteryCapacityLevel;
+import android.hardware.health.V2_1.Constants;
+import android.hardware.health.V2_1.IHealthInfoCallback;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * On service registration, {@link HealthServiceWrapperHidl.Callback#onRegistration} is called,
+ * which registers {@code this}, a {@link IHealthInfoCallback}, to the health service.
+ *
+ * <p>When the health service has updates to health info, {@link HealthInfoCallback#update} is
+ * called.
+ *
+ * @hide
+ */
+class HealthHalCallbackHidl extends IHealthInfoCallback.Stub
+        implements HealthServiceWrapperHidl.Callback {
+
+    private static final String TAG = HealthHalCallbackHidl.class.getSimpleName();
+
+    private static void traceBegin(String name) {
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
+    }
+
+    private static void traceEnd() {
+        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+    }
+
+    private HealthInfoCallback mCallback;
+
+    HealthHalCallbackHidl(@NonNull HealthInfoCallback callback) {
+        mCallback = callback;
+    }
+
+    @Override
+    public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
+        android.hardware.health.V2_1.HealthInfo propsLatest =
+                new android.hardware.health.V2_1.HealthInfo();
+        propsLatest.legacy = props;
+
+        propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED;
+        propsLatest.batteryChargeTimeToFullNowSeconds =
+                Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED;
+
+        mCallback.update(propsLatest);
+    }
+
+    @Override
+    public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) {
+        mCallback.update(props);
+    }
+
+    // on new service registered
+    @Override
+    public void onRegistration(IHealth oldService, IHealth newService, String instance) {
+        if (newService == null) return;
+
+        traceBegin("HealthUnregisterCallback");
+        try {
+            if (oldService != null) {
+                int r = oldService.unregisterCallback(this);
+                if (r != Result.SUCCESS) {
+                    Slog.w(
+                            TAG,
+                            "health: cannot unregister previous callback: " + Result.toString(r));
+                }
+            }
+        } catch (RemoteException ex) {
+            Slog.w(
+                    TAG,
+                    "health: cannot unregister previous callback (transaction error): "
+                            + ex.getMessage());
+        } finally {
+            traceEnd();
+        }
+
+        traceBegin("HealthRegisterCallback");
+        try {
+            int r = newService.registerCallback(this);
+            if (r != Result.SUCCESS) {
+                Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
+                return;
+            }
+            // registerCallback does NOT guarantee that update is called
+            // immediately, so request a manual update here.
+            newService.update();
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "health: cannot register callback (transaction error): " + ex.getMessage());
+        } finally {
+            traceEnd();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/health/HealthInfoCallback.java b/services/core/java/com/android/server/health/HealthInfoCallback.java
new file mode 100644
index 0000000..8136ca0
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthInfoCallback.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.health;
+
+/**
+ * A wrapper over HIDL / AIDL IHealthInfoCallback.
+ *
+ * @hide
+ */
+public interface HealthInfoCallback {
+    /**
+     * Signals to the client that health info is changed.
+     *
+     * @param props the new health info.
+     */
+    // TODO(b/177269435): AIDL
+    void update(android.hardware.health.V2_1.HealthInfo props);
+}
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapper.java b/services/core/java/com/android/server/health/HealthServiceWrapper.java
new file mode 100644
index 0000000..0b43f26
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthServiceWrapper.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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.health;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryProperty;
+import android.os.HandlerThread;
+import android.os.IBatteryPropertiesRegistrar;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.NoSuchElementException;
+
+/**
+ * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when necessary.
+ * This is essentially a wrapper over IHealth that is useful for BatteryService.
+ *
+ * <p>The implementation may be backed by a HIDL or AIDL HAL.
+ *
+ * <p>On new registration of IHealth service, the internal service is refreshed. On death of an
+ * existing IHealth service, the internal service is NOT cleared to avoid race condition between
+ * death notification and new service notification. Hence, a caller must check for transaction
+ * errors when calling into the service.
+ *
+ * @hide Should only be used internally.
+ */
+public abstract class HealthServiceWrapper {
+    /** @return the handler thread. Exposed for testing. */
+    @VisibleForTesting
+    abstract HandlerThread getHandlerThread();
+
+    /**
+     * Calls into get*() functions in the health HAL. This reads into the kernel interfaces
+     * directly.
+     *
+     * @see IBatteryPropertiesRegistrar#getProperty
+     */
+    public abstract int getProperty(int id, BatteryProperty prop) throws RemoteException;
+
+    /**
+     * Calls update() in the health HAL.
+     *
+     * @see IBatteryPropertiesRegistrar#scheduleUpdate
+     */
+    public abstract void scheduleUpdate() throws RemoteException;
+
+    /**
+     * Calls into getHealthInfo() in the health HAL. This returns a cached value in the health HAL
+     * implementation.
+     *
+     * @return health info. {@code null} if no health HAL service. {@code null} if any
+     *     service-specific error when calling {@code getHealthInfo}, e.g. it is unsupported.
+     * @throws RemoteException for any transaction-level errors
+     */
+    // TODO(b/177269435): AIDL
+    public abstract android.hardware.health.V1_0.HealthInfo getHealthInfo() throws RemoteException;
+
+    /**
+     * Create a new HealthServiceWrapper instance.
+     *
+     * @param healthInfoCallback the callback to call when health info changes
+     * @return the new HealthServiceWrapper instance, which may be backed by HIDL or AIDL service.
+     * @throws RemoteException transaction errors
+     * @throws NoSuchElementException no HIDL or AIDL service is available
+     */
+    public static HealthServiceWrapper create(@Nullable HealthInfoCallback healthInfoCallback)
+            throws RemoteException, NoSuchElementException {
+        return create(
+                healthInfoCallback == null ? null : new HealthHalCallbackHidl(healthInfoCallback),
+                new HealthServiceWrapperHidl.IServiceManagerSupplier() {},
+                new HealthServiceWrapperHidl.IHealthSupplier() {});
+    }
+
+    /**
+     * Create a new HealthServiceWrapper instance for testing.
+     *
+     * @param hidlRegCallback callback for HIDL service registration, or {@code null} if the client
+     *     does not care about HIDL service registration notifications
+     * @param hidlServiceManagerSupplier supplier of HIDL service manager
+     * @param hidlHealthSupplier supplier of HIDL health HAL
+     * @return the new HealthServiceWrapper instance, which may be backed by HIDL or AIDL service.
+     */
+    @VisibleForTesting
+    static @NonNull HealthServiceWrapper create(
+            @Nullable HealthServiceWrapperHidl.Callback hidlRegCallback,
+            @NonNull HealthServiceWrapperHidl.IServiceManagerSupplier hidlServiceManagerSupplier,
+            @NonNull HealthServiceWrapperHidl.IHealthSupplier hidlHealthSupplier)
+            throws RemoteException, NoSuchElementException {
+        return new HealthServiceWrapperHidl(
+                hidlRegCallback, hidlServiceManagerSupplier, hidlHealthSupplier);
+    }
+}
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java
new file mode 100644
index 0000000..3bff2f8
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2021 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.health;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.health.V1_0.HealthInfo;
+import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.BatteryManager;
+import android.os.BatteryProperty;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.MutableInt;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Implement {@link HealthServiceWrapper} backed by the HIDL HAL.
+ *
+ * @hide
+ */
+final class HealthServiceWrapperHidl extends HealthServiceWrapper {
+    private static final String TAG = "HealthServiceWrapperHidl";
+    public static final String INSTANCE_VENDOR = "default";
+
+    private final IServiceNotification mNotification = new Notification();
+    private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder");
+    // These variables are fixed after init.
+    private Callback mCallback;
+    private IHealthSupplier mHealthSupplier;
+    private String mInstanceName;
+
+    // Last IHealth service received.
+    private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
+
+    private static void traceBegin(String name) {
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
+    }
+
+    private static void traceEnd() {
+        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+    }
+
+    @Override
+    public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+        traceBegin("HealthGetProperty");
+        try {
+            IHealth service = mLastService.get();
+            if (service == null) throw new RemoteException("no health service");
+            final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+            switch (id) {
+                case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+                    service.getChargeCounter(
+                            (int result, int value) -> {
+                                outResult.value = result;
+                                if (result == Result.SUCCESS) prop.setLong(value);
+                            });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+                    service.getCurrentNow(
+                            (int result, int value) -> {
+                                outResult.value = result;
+                                if (result == Result.SUCCESS) prop.setLong(value);
+                            });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+                    service.getCurrentAverage(
+                            (int result, int value) -> {
+                                outResult.value = result;
+                                if (result == Result.SUCCESS) prop.setLong(value);
+                            });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+                    service.getCapacity(
+                            (int result, int value) -> {
+                                outResult.value = result;
+                                if (result == Result.SUCCESS) prop.setLong(value);
+                            });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_STATUS:
+                    service.getChargeStatus(
+                            (int result, int value) -> {
+                                outResult.value = result;
+                                if (result == Result.SUCCESS) prop.setLong(value);
+                            });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+                    service.getEnergyCounter(
+                            (int result, long value) -> {
+                                outResult.value = result;
+                                if (result == Result.SUCCESS) prop.setLong(value);
+                            });
+                    break;
+            }
+            return outResult.value;
+        } finally {
+            traceEnd();
+        }
+    }
+
+    @Override
+    public void scheduleUpdate() throws RemoteException {
+        getHandlerThread()
+                .getThreadHandler()
+                .post(
+                        () -> {
+                            traceBegin("HealthScheduleUpdate");
+                            try {
+                                IHealth service = mLastService.get();
+                                if (service == null) {
+                                    Slog.e(TAG, "no health service");
+                                    return;
+                                }
+                                service.update();
+                            } catch (RemoteException ex) {
+                                Slog.e(TAG, "Cannot call update on health HAL", ex);
+                            } finally {
+                                traceEnd();
+                            }
+                        });
+    }
+
+    private static class Mutable<T> {
+        public T value;
+    }
+
+    @Override
+    public HealthInfo getHealthInfo() throws RemoteException {
+        IHealth service = mLastService.get();
+        if (service == null) return null;
+        final Mutable<HealthInfo> ret = new Mutable<>();
+        service.getHealthInfo(
+                (result, value) -> {
+                    if (result == Result.SUCCESS) {
+                        ret.value = value.legacy;
+                    }
+                });
+        return ret.value;
+    }
+
+    /**
+     * Start monitoring registration of new IHealth services. Only instance {@link #INSTANCE_VENDOR}
+     * and in device / framework manifest are used. This function should only be called once.
+     *
+     * <p>mCallback.onRegistration() is called synchronously (aka in init thread) before this method
+     * returns if callback is not null.
+     *
+     * @throws RemoteException transaction error when talking to IServiceManager
+     * @throws NoSuchElementException if one of the following cases: - No service manager; - {@link
+     *     #INSTANCE_VENDOR} is not in manifests (i.e. not available on this device), or none of
+     *     these instances are available to current process.
+     * @throws NullPointerException when supplier is null
+     */
+    @VisibleForTesting
+    HealthServiceWrapperHidl(
+            @Nullable Callback callback,
+            @NonNull IServiceManagerSupplier managerSupplier,
+            @NonNull IHealthSupplier healthSupplier)
+            throws RemoteException, NoSuchElementException, NullPointerException {
+        if (managerSupplier == null || healthSupplier == null) {
+            throw new NullPointerException();
+        }
+        mHealthSupplier = healthSupplier;
+
+        // Initialize mLastService and call callback for the first time (in init thread)
+        IHealth newService = null;
+        traceBegin("HealthInitGetService_" + INSTANCE_VENDOR);
+        try {
+            newService = healthSupplier.get(INSTANCE_VENDOR);
+        } catch (NoSuchElementException ex) {
+            /* ignored, handled below */
+        } finally {
+            traceEnd();
+        }
+        if (newService != null) {
+            mInstanceName = INSTANCE_VENDOR;
+            mLastService.set(newService);
+        }
+
+        if (mInstanceName == null || newService == null) {
+            throw new NoSuchElementException(
+                    String.format(
+                            "IHealth service instance %s isn't available. Perhaps no permission?",
+                            INSTANCE_VENDOR));
+        }
+
+        if (callback != null) {
+            mCallback = callback;
+            mCallback.onRegistration(null, newService, mInstanceName);
+        }
+
+        // Register for future service registrations
+        traceBegin("HealthInitRegisterNotification");
+        mHandlerThread.start();
+        try {
+            managerSupplier
+                    .get()
+                    .registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification);
+        } finally {
+            traceEnd();
+        }
+        Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
+    }
+
+    @VisibleForTesting
+    public HandlerThread getHandlerThread() {
+        return mHandlerThread;
+    }
+
+    /** Service registration callback. */
+    interface Callback {
+        /**
+         * This function is invoked asynchronously when a new and related IServiceNotification is
+         * received.
+         *
+         * @param service the recently retrieved service from IServiceManager. Can be a dead service
+         *     before service notification of a new service is delivered. Implementation must handle
+         *     cases for {@link RemoteException}s when calling into service.
+         * @param instance instance name.
+         */
+        void onRegistration(IHealth oldService, IHealth newService, String instance);
+    }
+
+    /**
+     * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service
+     * is not available.
+     */
+    interface IServiceManagerSupplier {
+        default IServiceManager get() throws NoSuchElementException, RemoteException {
+            return IServiceManager.getService();
+        }
+    }
+
+    /**
+     * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service
+     * is not available.
+     */
+    interface IHealthSupplier {
+        default IHealth get(String name) throws NoSuchElementException, RemoteException {
+            return IHealth.getService(name, true /* retry */);
+        }
+    }
+
+    private class Notification extends IServiceNotification.Stub {
+        @Override
+        public final void onRegistration(
+                String interfaceName, String instanceName, boolean preexisting) {
+            if (!IHealth.kInterfaceName.equals(interfaceName)) return;
+            if (!mInstanceName.equals(instanceName)) return;
+
+            // This runnable only runs on mHandlerThread and ordering is ensured, hence
+            // no locking is needed inside the runnable.
+            mHandlerThread
+                    .getThreadHandler()
+                    .post(
+                            new Runnable() {
+                                @Override
+                                public void run() {
+                                    try {
+                                        IHealth newService = mHealthSupplier.get(mInstanceName);
+                                        IHealth oldService = mLastService.getAndSet(newService);
+
+                                        // preexisting may be inaccurate (race). Check for equality
+                                        // here.
+                                        if (Objects.equals(newService, oldService)) return;
+
+                                        Slog.i(
+                                                TAG,
+                                                "health: new instance registered " + mInstanceName);
+                                        // #init() may be called with null callback. Skip null
+                                        // callbacks.
+                                        if (mCallback == null) return;
+                                        mCallback.onRegistration(
+                                                oldService, newService, mInstanceName);
+                                    } catch (NoSuchElementException | RemoteException ex) {
+                                        Slog.e(
+                                                TAG,
+                                                "health: Cannot get instance '"
+                                                        + mInstanceName
+                                                        + "': "
+                                                        + ex.getMessage()
+                                                        + ". Perhaps no permission?");
+                                    }
+                                }
+                            });
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/health/Utils.java b/services/core/java/com/android/server/health/Utils.java
new file mode 100644
index 0000000..fc039eb
--- /dev/null
+++ b/services/core/java/com/android/server/health/Utils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.health;
+
+/**
+ * Utils for {@link om.android.server.BatteryService} to deal with health info structs.
+ *
+ * @hide
+ */
+public class Utils {
+    private Utils() {}
+
+    /**
+     * Copy health info struct.
+     *
+     * @param dst destination
+     * @param src source
+     */
+    public static void copy(
+            android.hardware.health.V1_0.HealthInfo dst,
+            android.hardware.health.V1_0.HealthInfo src) {
+        dst.chargerAcOnline = src.chargerAcOnline;
+        dst.chargerUsbOnline = src.chargerUsbOnline;
+        dst.chargerWirelessOnline = src.chargerWirelessOnline;
+        dst.maxChargingCurrent = src.maxChargingCurrent;
+        dst.maxChargingVoltage = src.maxChargingVoltage;
+        dst.batteryStatus = src.batteryStatus;
+        dst.batteryHealth = src.batteryHealth;
+        dst.batteryPresent = src.batteryPresent;
+        dst.batteryLevel = src.batteryLevel;
+        dst.batteryVoltage = src.batteryVoltage;
+        dst.batteryTemperature = src.batteryTemperature;
+        dst.batteryCurrent = src.batteryCurrent;
+        dst.batteryCycleCount = src.batteryCycleCount;
+        dst.batteryFullCharge = src.batteryFullCharge;
+        dst.batteryChargeCounter = src.batteryChargeCounter;
+        dst.batteryTechnology = src.batteryTechnology;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index d04aac2..471c9b9 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -32,6 +32,7 @@
 import android.util.Slog;
 
 import com.android.server.notification.NotificationManagerService.DumpFilter;
+import com.android.server.pm.PackageManagerService;
 
 import java.io.PrintWriter;
 
@@ -114,11 +115,7 @@
         mIsAlarm = ZenModeConfig.isValidCountdownToAlarmConditionId(conditionId);
         final AlarmManager alarms = (AlarmManager)
                 mContext.getSystemService(Context.ALARM_SERVICE);
-        final Intent intent = new Intent(ACTION)
-                .putExtra(EXTRA_CONDITION_ID, conditionId)
-                .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
-                intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final PendingIntent pendingIntent = getPendingIntent(conditionId);
         alarms.cancel(pendingIntent);
         if (mTime > 0) {
             final long now = System.currentTimeMillis();
@@ -138,6 +135,16 @@
         }
     }
 
+    PendingIntent getPendingIntent(Uri conditionId) {
+        final Intent intent = new Intent(ACTION)
+                .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+                .putExtra(EXTRA_CONDITION_ID, conditionId)
+                .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
+                intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        return pendingIntent;
+    }
+
     @Override
     public void onUnsubscribe(Uri conditionId) {
         // noop
diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java
index 25ad9280..4be4f0a 100644
--- a/services/core/java/com/android/server/notification/EventConditionProvider.java
+++ b/services/core/java/com/android/server/notification/EventConditionProvider.java
@@ -41,6 +41,7 @@
 
 import com.android.server.notification.CalendarTracker.CheckEventResult;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
+import com.android.server.pm.PackageManagerService;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -266,12 +267,7 @@
     private void rescheduleAlarm(long now, long time) {
         mNextAlarmTime = time;
         final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
-                REQUEST_CODE_EVALUATE,
-                new Intent(ACTION_EVALUATE)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                        .putExtra(EXTRA_TIME, time),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        final PendingIntent pendingIntent = getPendingIntent(time);
         alarms.cancel(pendingIntent);
         if (time == 0 || time < now) {
             if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified"
@@ -283,6 +279,17 @@
         alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
     }
 
+    PendingIntent getPendingIntent(long time) {
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+                REQUEST_CODE_EVALUATE,
+                new Intent(ACTION_EVALUATE)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                        .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+                        .putExtra(EXTRA_TIME, time),
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        return pendingIntent;
+    }
+
     private Condition createCondition(Uri id, int state) {
         final String summary = NOT_SHOWN;
         final String line1 = NOT_SHOWN;
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index d8107fc..b792a17 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -83,19 +83,15 @@
     private final UserManagerInternal mUserManagerInternal;
     private final PermissionManagerServiceInternal mPermissionManager;
     private final RemovePackageHelper mRemovePackageHelper;
-    // TODO(b/201815903): remove dependency to InitAndSystemPackageHelper
-    private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
     private final AppDataHelper mAppDataHelper;
 
     // TODO(b/198166813): remove PMS dependency
     DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper,
-            InitAndSystemPackageHelper initAndSystemPackageHelper,
             AppDataHelper appDataHelper) {
         mPm = pm;
         mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
         mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
         mRemovePackageHelper = removePackageHelper;
-        mInitAndSystemPackageHelper = initAndSystemPackageHelper;
         mAppDataHelper = appDataHelper;
     }
 
@@ -105,7 +101,6 @@
         mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
         mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
         mRemovePackageHelper = new RemovePackageHelper(mPm, mAppDataHelper);
-        mInitAndSystemPackageHelper = mPm.getInitAndSystemPackageHelper();
     }
 
     /**
@@ -282,7 +277,7 @@
                             Slog.i(TAG, "Enabling system stub after removal; pkg: "
                                     + stubPkg.getPackageName());
                         }
-                        mInitAndSystemPackageHelper.enableCompressedPackage(stubPkg, stubPs);
+                        new InstallPackageHelper(mPm).enableCompressedPackage(stubPkg, stubPs);
                     } else if (DEBUG_COMPRESSION) {
                         Slog.i(TAG, "System stub disabled for all users, leaving uncompressed "
                                 + "after removal; pkg: " + stubPkg.getPackageName());
@@ -422,7 +417,7 @@
             // as well and fall back to existing code in system partition
             PackageSetting disabledPs = deleteInstalledSystemPackage(action, ps, allUserHandles,
                     flags, outInfo, writeSettings);
-            mInitAndSystemPackageHelper.restoreDisabledSystemPackageLIF(
+            new InstallPackageHelper(mPm).restoreDisabledSystemPackageLIF(
                     action, ps, allUserHandles, outInfo, writeSettings, disabledPs);
         } else {
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName());
diff --git a/services/core/java/com/android/server/pm/DomainVerificationConnection.java b/services/core/java/com/android/server/pm/DomainVerificationConnection.java
index 0ee0989..975ab4c1 100644
--- a/services/core/java/com/android/server/pm/DomainVerificationConnection.java
+++ b/services/core/java/com/android/server/pm/DomainVerificationConnection.java
@@ -42,14 +42,12 @@
     final PackageManagerService mPm;
     final PackageManagerInternal mPmInternal;
     final UserManagerInternal mUmInternal;
-    final VerificationHelper mVerificationHelper;
 
     // TODO(b/198166813): remove PMS dependency
     DomainVerificationConnection(PackageManagerService pm) {
         mPm = pm;
         mPmInternal = mPm.mInjector.getLocalService(PackageManagerInternal.class);
         mUmInternal = mPm.mInjector.getLocalService(UserManagerInternal.class);
-        mVerificationHelper = new VerificationHelper(mPm.mContext);
     }
 
     @Override
@@ -80,7 +78,7 @@
 
     @Override
     public long getPowerSaveTempWhitelistAppDuration() {
-        return mVerificationHelper.getVerificationTimeout();
+        return VerificationUtils.getVerificationTimeout(mPm.mContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/HandlerParams.java b/services/core/java/com/android/server/pm/HandlerParams.java
index 034688b..57a8a7a 100644
--- a/services/core/java/com/android/server/pm/HandlerParams.java
+++ b/services/core/java/com/android/server/pm/HandlerParams.java
@@ -16,22 +16,12 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-
-import android.annotation.NonNull;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInfoLite;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.util.Pair;
-import android.util.Slog;
-
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.TAG;
 
-import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+import android.util.Slog;
 
 abstract class HandlerParams {
     /** User handle for the user requesting the information or installation. */
@@ -40,15 +30,13 @@
     int mTraceCookie;
     @NonNull
     final PackageManagerService mPm;
-    final VerificationHelper mVerificationHelper;
-    final BroadcastHelper mBroadcastHelper;
+    final InstallPackageHelper mInstallPackageHelper;
 
     // TODO(b/198166813): remove PMS dependency
     HandlerParams(UserHandle user, PackageManagerService pm) {
         mUser = user;
         mPm = pm;
-        mVerificationHelper = new VerificationHelper(mPm.mContext);
-        mBroadcastHelper = new BroadcastHelper(mPm.mInjector);
+        mInstallPackageHelper = new InstallPackageHelper(mPm);
     }
 
     UserHandle getUser() {
@@ -73,135 +61,4 @@
 
     abstract void handleStartCopy();
     abstract void handleReturnCode();
-
-    Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
-            long requiredInstalledVersionCode, int installFlags) {
-        if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
-            return verifyReplacingVersionCodeForApex(
-                    pkgLite, requiredInstalledVersionCode, installFlags);
-        }
-
-        String packageName = pkgLite.packageName;
-        synchronized (mPm.mLock) {
-            // Package which currently owns the data that the new package will own if installed.
-            // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
-            // will be null whereas dataOwnerPkg will contain information about the package
-            // which was uninstalled while keeping its data.
-            AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName);
-            if (dataOwnerPkg  == null) {
-                PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    dataOwnerPkg = ps.getPkg();
-                }
-            }
-
-            if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
-                if (dataOwnerPkg == null) {
-                    String errorMsg = "Required installed version code was "
-                            + requiredInstalledVersionCode
-                            + " but package is not installed";
-                    Slog.w(TAG, errorMsg);
-                    return Pair.create(
-                            PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
-                }
-
-                if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
-                    String errorMsg = "Required installed version code was "
-                            + requiredInstalledVersionCode
-                            + " but actual installed version is "
-                            + dataOwnerPkg.getLongVersionCode();
-                    Slog.w(TAG, errorMsg);
-                    return Pair.create(
-                            PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
-                }
-            }
-
-            if (dataOwnerPkg != null) {
-                if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
-                        dataOwnerPkg.isDebuggable())) {
-                    try {
-                        checkDowngrade(dataOwnerPkg, pkgLite);
-                    } catch (PackageManagerException e) {
-                        String errorMsg = "Downgrade detected: " + e.getMessage();
-                        Slog.w(TAG, errorMsg);
-                        return Pair.create(
-                                PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
-                    }
-                }
-            }
-        }
-        return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
-    }
-
-    private Pair<Integer, String> verifyReplacingVersionCodeForApex(PackageInfoLite pkgLite,
-            long requiredInstalledVersionCode, int installFlags) {
-        String packageName = pkgLite.packageName;
-
-        final PackageInfo activePackage = mPm.mApexManager.getPackageInfo(packageName,
-                ApexManager.MATCH_ACTIVE_PACKAGE);
-        if (activePackage == null) {
-            String errorMsg = "Attempting to install new APEX package " + packageName;
-            Slog.w(TAG, errorMsg);
-            return Pair.create(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED, errorMsg);
-        }
-
-        final long activeVersion = activePackage.getLongVersionCode();
-        if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST
-                && activeVersion != requiredInstalledVersionCode) {
-            String errorMsg = "Installed version of APEX package " + packageName
-                    + " does not match required. Active version: " + activeVersion
-                    + " required: " + requiredInstalledVersionCode;
-            Slog.w(TAG, errorMsg);
-            return Pair.create(PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
-        }
-
-        final boolean isAppDebuggable = (activePackage.applicationInfo.flags
-                & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
-        final long newVersionCode = pkgLite.getLongVersionCode();
-        if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, isAppDebuggable)
-                && newVersionCode < activeVersion) {
-            String errorMsg = "Downgrade of APEX package " + packageName
-                    + " is not allowed. Active version: " + activeVersion
-                    + " attempted: " + newVersionCode;
-            Slog.w(TAG, errorMsg);
-            return Pair.create(PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
-        }
-
-        return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
-    }
-
-    /**
-     * Check and throw if the given before/after packages would be considered a
-     * downgrade.
-     */
-    private static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
-            throws PackageManagerException {
-        if (after.getLongVersionCode() < before.getLongVersionCode()) {
-            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                    "Update version code " + after.versionCode + " is older than current "
-                            + before.getLongVersionCode());
-        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
-            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
-                throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                        "Update base revision code " + after.baseRevisionCode
-                                + " is older than current " + before.getBaseRevisionCode());
-            }
-
-            if (!ArrayUtils.isEmpty(after.splitNames)) {
-                for (int i = 0; i < after.splitNames.length; i++) {
-                    final String splitName = after.splitNames[i];
-                    final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
-                    if (j != -1) {
-                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
-                            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                                    "Update split " + splitName + " revision code "
-                                            + after.splitRevisionCodes[i]
-                                            + " is older than current "
-                                            + before.getSplitRevisionCodes()[j]);
-                        }
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 5ff0a6f..239a9d71 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -16,22 +16,13 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
-import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
-import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
-import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
-import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME;
-import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
-import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
-import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
@@ -42,31 +33,21 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerServiceUtils.decompressFile;
-import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
-import static com.android.server.pm.PackageManagerServiceUtils.makeDirRecursive;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ContentResolver;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Environment;
-import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.os.UserHandle;
-import android.system.ErrnoException;
 import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -74,15 +55,10 @@
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.utils.WatchedArrayMap;
 
-import libcore.io.IoUtils;
-
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 
@@ -270,6 +246,8 @@
                  * If the package is scanned, it's not erased.
                  */
                 final AndroidPackage scannedPkg = mPm.mPackages.get(ps.getPackageName());
+                final PackageSetting disabledPs =
+                        mPm.mSettings.getDisabledSystemPkgLPr(ps.getPackageName());
                 if (scannedPkg != null) {
                     /*
                      * If the system app is both scanned and in the
@@ -278,7 +256,7 @@
                      * scanned package so the previously user-installed
                      * application can be scanned.
                      */
-                    if (mPm.mSettings.isDisabledSystemPackageLPr(ps.getPackageName())) {
+                    if (disabledPs != null) {
                         logCriticalInfo(Log.WARN,
                                 "Expecting better updated system app for "
                                         + ps.getPackageName()
@@ -294,7 +272,7 @@
                     continue;
                 }
 
-                if (!mPm.mSettings.isDisabledSystemPackageLPr(ps.getPackageName())) {
+                if (disabledPs == null) {
                     logCriticalInfo(Log.WARN, "System package " + ps.getPackageName()
                             + " no longer exists; its data will be wiped");
                     mRemovePackageHelper.removePackageDataLIF(ps, userIds, null, 0, false);
@@ -303,8 +281,6 @@
                     // been removed. check the code path still exists and check there's
                     // still a package. the latter can happen if an OTA keeps the same
                     // code path, but, changes the package name.
-                    final PackageSetting disabledPs =
-                            mPm.mSettings.getDisabledSystemPkgLPr(ps.getPackageName());
                     if (disabledPs.getPath() == null || !disabledPs.getPath().exists()
                             || disabledPs.getPkg() == null) {
                         possiblyDeletedUpdatedSystemApps.add(ps.getPackageName());
@@ -454,7 +430,7 @@
 
             // Uncompress and install any stubbed system applications.
             // This must be done last to ensure all stubs are replaced or disabled.
-            installSystemStubPackages(stubSystemApps, mScanFlags);
+            new InstallPackageHelper(mPm).installSystemStubPackages(stubSystemApps, mScanFlags);
 
             final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
                     - cachedSystemApps;
@@ -568,403 +544,11 @@
         }
     }
 
-    /**
-     * Uncompress and install stub applications.
-     * <p>In order to save space on the system partition, some applications are shipped in a
-     * compressed form. In addition the compressed bits for the full application, the
-     * system image contains a tiny stub comprised of only the Android manifest.
-     * <p>During the first boot, attempt to uncompress and install the full application. If
-     * the application can't be installed for any reason, disable the stub and prevent
-     * uncompressing the full application during future boots.
-     * <p>In order to forcefully attempt an installation of a full application, go to app
-     * settings and enable the application.
-     */
-    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
-    private void installSystemStubPackages(@NonNull List<String> systemStubPackageNames,
-            @PackageManagerService.ScanFlags int scanFlags) {
-        for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) {
-            final String packageName = systemStubPackageNames.get(i);
-            // skip if the system package is already disabled
-            if (mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
-                systemStubPackageNames.remove(i);
-                continue;
-            }
-            // skip if the package isn't installed (?!); this should never happen
-            final AndroidPackage pkg = mPm.mPackages.get(packageName);
-            if (pkg == null) {
-                systemStubPackageNames.remove(i);
-                continue;
-            }
-            // skip if the package has been disabled by the user
-            final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
-            if (ps != null) {
-                final int enabledState = ps.getEnabled(UserHandle.USER_SYSTEM);
-                if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
-                    systemStubPackageNames.remove(i);
-                    continue;
-                }
-            }
-
-            // install the package to replace the stub on /system
-            try {
-                installStubPackageLI(pkg, 0, scanFlags);
-                ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
-                        UserHandle.USER_SYSTEM, "android");
-                systemStubPackageNames.remove(i);
-            } catch (PackageManagerException e) {
-                Slog.e(TAG, "Failed to parse uncompressed system package: " + e.getMessage());
-            }
-
-            // any failed attempt to install the package will be cleaned up later
-        }
-
-        // disable any stub still left; these failed to install the full application
-        for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) {
-            final String pkgName = systemStubPackageNames.get(i);
-            final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
-            ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                    UserHandle.USER_SYSTEM, "android");
-            logCriticalInfo(Log.ERROR, "Stub disabled; pkg: " + pkgName);
-        }
-    }
-
-    /**
-     * Extract, install and enable a stub package.
-     * <p>If the compressed file can not be extracted / installed for any reason, the stub
-     * APK will be installed and the package will be disabled. To recover from this situation,
-     * the user will need to go into system settings and re-enable the package.
-     */
-    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
-    public boolean enableCompressedPackage(AndroidPackage stubPkg,
-            @NonNull PackageSetting stubPkgSetting) {
-        final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
-                | ParsingPackageUtils.PARSE_ENFORCE_CODE;
-        synchronized (mPm.mInstallLock) {
-            final AndroidPackage pkg;
-            try (PackageFreezer freezer =
-                         mPm.freezePackage(stubPkg.getPackageName(), "setEnabledSetting")) {
-                pkg = installStubPackageLI(stubPkg, parseFlags, 0 /*scanFlags*/);
-                synchronized (mPm.mLock) {
-                    mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
-                    try {
-                        mPm.updateSharedLibrariesLocked(pkg, stubPkgSetting, null, null,
-                                Collections.unmodifiableMap(mPm.mPackages));
-                    } catch (PackageManagerException e) {
-                        Slog.w(TAG, "updateAllSharedLibrariesLPw failed: ", e);
-                    }
-                    mPm.mPermissionManager.onPackageInstalled(pkg,
-                            Process.INVALID_UID /* previousAppId */,
-                            PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
-                            UserHandle.USER_ALL);
-                    mPm.writeSettingsLPrTEMP();
-                }
-            } catch (PackageManagerException e) {
-                // Whoops! Something went very wrong; roll back to the stub and disable the package
-                try (PackageFreezer freezer =
-                             mPm.freezePackage(stubPkg.getPackageName(), "setEnabledSetting")) {
-                    synchronized (mPm.mLock) {
-                        // NOTE: Ensure the system package is enabled; even for a compressed stub.
-                        // If we don't, installing the system package fails during scan
-                        enableSystemPackageLPw(stubPkg);
-                    }
-                    installPackageFromSystemLIF(stubPkg.getPath(),
-                            mPm.mUserManager.getUserIds() /*allUserHandles*/,
-                            null /*origUserHandles*/,
-                            true /*writeSettings*/);
-                } catch (PackageManagerException pme) {
-                    // Serious WTF; we have to be able to install the stub
-                    Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(),
-                            pme);
-                } finally {
-                    // Disable the package; the stub by itself is not runnable
-                    synchronized (mPm.mLock) {
-                        final PackageSetting stubPs = mPm.mSettings.getPackageLPr(
-                                stubPkg.getPackageName());
-                        if (stubPs != null) {
-                            stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED,
-                                    UserHandle.USER_SYSTEM, "android");
-                        }
-                        mPm.writeSettingsLPrTEMP();
-                    }
-                }
-                return false;
-            }
-            mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
-                    FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
-                            | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-            mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
-                    pkg.getBaseApkPath(), pkg.getSplitCodePaths());
-        }
-        return true;
-    }
-
-    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
-    private AndroidPackage installStubPackageLI(AndroidPackage stubPkg,
-            @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags)
-            throws PackageManagerException {
-        if (DEBUG_COMPRESSION) {
-            Slog.i(TAG, "Uncompressing system stub; pkg: " + stubPkg.getPackageName());
-        }
-        // uncompress the binary to its eventual destination on /data
-        final File scanFile = decompressPackage(stubPkg.getPackageName(), stubPkg.getPath());
-        if (scanFile == null) {
-            throw new PackageManagerException(
-                    "Unable to decompress stub at " + stubPkg.getPath());
-        }
-        synchronized (mPm.mLock) {
-            mPm.mSettings.disableSystemPackageLPw(stubPkg.getPackageName(), true /*replaced*/);
-        }
-        mRemovePackageHelper.removePackageLI(stubPkg, true /*chatty*/);
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mPm);
-        try {
-            return scanPackageHelper.scanPackageTracedLI(scanFile, parseFlags, scanFlags, 0, null);
-        } catch (PackageManagerException e) {
-            Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
-                    e);
-            // Remove the failed install
-            mRemovePackageHelper.removeCodePathLI(scanFile);
-            throw e;
-        }
-    }
-
-    /**
-     * Decompresses the given package on the system image onto
-     * the /data partition.
-     * @return The directory the package was decompressed into. Otherwise, {@code null}.
-     */
-    @GuardedBy("mPm.mInstallLock")
-    private File decompressPackage(String packageName, String codePath) {
-        final File[] compressedFiles = getCompressedFiles(codePath);
-        if (compressedFiles == null || compressedFiles.length == 0) {
-            if (DEBUG_COMPRESSION) {
-                Slog.i(TAG, "No files to decompress: " + codePath);
-            }
-            return null;
-        }
-        final File dstCodePath =
-                PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null),
-                        packageName);
-        int ret = PackageManager.INSTALL_SUCCEEDED;
-        try {
-            makeDirRecursive(dstCodePath, 0755);
-            for (File srcFile : compressedFiles) {
-                final String srcFileName = srcFile.getName();
-                final String dstFileName = srcFileName.substring(
-                        0, srcFileName.length() - COMPRESSED_EXTENSION.length());
-                final File dstFile = new File(dstCodePath, dstFileName);
-                ret = decompressFile(srcFile, dstFile);
-                if (ret != PackageManager.INSTALL_SUCCEEDED) {
-                    logCriticalInfo(Log.ERROR, "Failed to decompress"
-                            + "; pkg: " + packageName
-                            + ", file: " + dstFileName);
-                    break;
-                }
-            }
-        } catch (ErrnoException e) {
-            logCriticalInfo(Log.ERROR, "Failed to decompress"
-                    + "; pkg: " + packageName
-                    + ", err: " + e.errno);
-        }
-        if (ret == PackageManager.INSTALL_SUCCEEDED) {
-            final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME);
-            NativeLibraryHelper.Handle handle = null;
-            try {
-                handle = NativeLibraryHelper.Handle.create(dstCodePath);
-                ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                        null /*abiOverride*/, false /*isIncremental*/);
-            } catch (IOException e) {
-                logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
-                        + "; pkg: " + packageName);
-                ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-            } finally {
-                IoUtils.closeQuietly(handle);
-            }
-        }
-        if (ret == PackageManager.INSTALL_SUCCEEDED) {
-            // NOTE: During boot, we have to delay releasing cblocks for no other reason than
-            // we cannot retrieve the setting {@link Secure#RELEASE_COMPRESS_BLOCKS_ON_INSTALL}.
-            // When we no longer need to read that setting, cblock release can occur always
-            // occur here directly
-            if (!mPm.isSystemReady()) {
-                if (mPm.mReleaseOnSystemReady == null) {
-                    mPm.mReleaseOnSystemReady = new ArrayList<>();
-                }
-                mPm.mReleaseOnSystemReady.add(dstCodePath);
-            } else {
-                final ContentResolver resolver = mPm.mContext.getContentResolver();
-                F2fsUtils.releaseCompressedBlocks(resolver, dstCodePath);
-            }
-        }
-        if (ret != PackageManager.INSTALL_SUCCEEDED) {
-            if (!dstCodePath.exists()) {
-                return null;
-            }
-            mRemovePackageHelper.removeCodePathLI(dstCodePath);
-            return null;
-        }
-
-        return dstCodePath;
-    }
-
-
-    @GuardedBy("mPm.mLock")
-    private void enableSystemPackageLPw(AndroidPackage pkg) {
-        mPm.mSettings.enableSystemPackageLPw(pkg.getPackageName());
-    }
-
-    /**
-     * Tries to restore the disabled system package after an update has been deleted.
-     */
-    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
-    public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
-            PackageSetting deletedPs, @NonNull int[] allUserHandles,
-            @Nullable PackageRemovedInfo outInfo,
-            boolean writeSettings,
-            PackageSetting disabledPs)
-            throws SystemDeleteException {
-        // writer
-        synchronized (mPm.mLock) {
-            // NOTE: The system package always needs to be enabled; even if it's for
-            // a compressed stub. If we don't, installing the system package fails
-            // during scan [scanning checks the disabled packages]. We will reverse
-            // this later, after we've "installed" the stub.
-            // Reinstate the old system package
-            enableSystemPackageLPw(disabledPs.getPkg());
-            // Remove any native libraries from the upgraded package.
-            removeNativeBinariesLI(deletedPs);
-        }
-
-        // Install the system package
-        if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
-        try {
-            installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
-                    outInfo == null ? null : outInfo.mOrigUsers, writeSettings);
-        } catch (PackageManagerException e) {
-            Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
-                    + e.getMessage());
-            // TODO(b/194319951): can we avoid this; throw would come from scan...
-            throw new SystemDeleteException(e);
-        } finally {
-            if (disabledPs.getPkg().isStub()) {
-                // We've re-installed the stub; make sure it's disabled here. If package was
-                // originally enabled, we'll install the compressed version of the application
-                // and re-enable it afterward.
-                final PackageSetting stubPs = mPm.mSettings.getPackageLPr(
-                        deletedPs.getPackageName());
-                if (stubPs != null) {
-                    int userId = action.mUser == null
-                            ? UserHandle.USER_ALL : action.mUser.getIdentifier();
-                    if (userId == UserHandle.USER_ALL) {
-                        for (int aUserId : allUserHandles) {
-                            stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, aUserId, "android");
-                        }
-                    } else if (userId >= UserHandle.USER_SYSTEM) {
-                        stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, userId, "android");
-                    }
-                }
-            }
-        }
-    }
-
-    private void removeNativeBinariesLI(PackageSetting ps) {
-        if (ps != null) {
-            NativeLibraryHelper.removeNativeBinariesLI(ps.getLegacyNativeLibraryPath());
-        }
-    }
-
-    /**
-     * Installs a package that's already on the system partition.
-     */
-    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
-    private void installPackageFromSystemLIF(@NonNull String codePathString,
-            @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings)
-            throws PackageManagerException {
-        final File codePath = new File(codePathString);
-        @ParsingPackageUtils.ParseFlags int parseFlags =
-                mPm.getDefParseFlags()
-                        | ParsingPackageUtils.PARSE_MUST_BE_APK
-                        | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
-        @PackageManagerService.ScanFlags int scanFlags = SCAN_AS_SYSTEM;
-        for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
-            ScanPartition partition = mDirsToScanAsSystem.get(i);
-            if (partition.containsFile(codePath)) {
-                scanFlags |= partition.scanFlag;
-                if (partition.containsPrivApp(codePath)) {
-                    scanFlags |= SCAN_AS_PRIVILEGED;
-                }
-                break;
-            }
-        }
-
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mPm);
-        final AndroidPackage pkg =
-                scanPackageHelper.scanPackageTracedLI(
-                        codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
-
-        PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
-
-        try {
-            // update shared libraries for the newly re-installed system package
-            mPm.updateSharedLibrariesLocked(pkg, pkgSetting, null, null,
-                    Collections.unmodifiableMap(mPm.mPackages));
-        } catch (PackageManagerException e) {
-            Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
-        }
-
-        mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
-
-        // writer
-        synchronized (mPm.mLock) {
-            PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
-
-            final boolean applyUserRestrictions = origUserHandles != null;
-            if (applyUserRestrictions) {
-                boolean installedStateChanged = false;
-                if (DEBUG_REMOVE) {
-                    Slog.d(TAG, "Propagating install state across reinstall");
-                }
-                for (int userId : allUserHandles) {
-                    final boolean installed = ArrayUtils.contains(origUserHandles, userId);
-                    if (DEBUG_REMOVE) {
-                        Slog.d(TAG, "    user " + userId + " => " + installed);
-                    }
-                    if (installed != ps.getInstalled(userId)) {
-                        installedStateChanged = true;
-                    }
-                    ps.setInstalled(installed, userId);
-                    if (installed) {
-                        ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
-                    }
-                }
-                // Regardless of writeSettings we need to ensure that this restriction
-                // state propagation is persisted
-                mPm.mSettings.writeAllUsersPackageRestrictionsLPr();
-                if (installedStateChanged) {
-                    mPm.mSettings.writeKernelMappingLPr(ps);
-                }
-            }
-
-            // The method below will take care of removing obsolete permissions and granting
-            // install permissions.
-            mPm.mPermissionManager.onPackageInstalled(pkg,
-                    Process.INVALID_UID /* previousAppId */,
-                    PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
-                    UserHandle.USER_ALL);
-            for (final int userId : allUserHandles) {
-                if (applyUserRestrictions) {
-                    mPm.mSettings.writePermissionStateForUserLPr(userId, false);
-                }
-            }
-
-            // can downgrade to reader here
-            if (writeSettings) {
-                mPm.writeSettingsLPrTEMP();
-            }
-        }
-    }
-
     public boolean isExpectingBetter(String packageName) {
         return mExpectingBetter.containsKey(packageName);
     }
+
+    public List<ScanPartition> getDirsToScanAsSystem() {
+        return mDirsToScanAsSystem;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index dc5b85a..8ba8e45 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -16,41 +16,112 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_PERMISSION_GROUP;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID;
+import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
+import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
+import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
+import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
+import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
+import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
+import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
+import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
+import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
+import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.POST_INSTALL;
+import static com.android.server.pm.PackageManagerService.PRECOMPILE_LAYOUTS;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_ODM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 import static com.android.server.pm.PackageManagerService.SCAN_IGNORE_FROZEN;
+import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
+import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
+import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
+import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
+import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
 import static com.android.server.pm.PackageManagerService.TAG;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
+import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.app.ApplicationPackageManager;
 import android.app.backup.IBackupManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderType;
+import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.PackageChangeEvent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
+import android.content.pm.VerifierInfo;
+import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.component.ComponentMutateUtils;
 import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
@@ -60,34 +131,78 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.incremental.IncrementalManager;
+import android.os.incremental.IncrementalStorage;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.stats.storage.StorageEnums;
 import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.F2fsUtils;
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.EventLogTags;
+import com.android.server.Watchdog;
+import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.permission.Permission;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.utils.WatchedLongSparseArray;
 
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.DigestException;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 
 final class InstallPackageHelper {
+    /**
+     * Whether verification is enabled by default.
+     */
+    private static final boolean DEFAULT_VERIFY_ENABLE = true;
+
     private final PackageManagerService mPm;
     private final AppDataHelper mAppDataHelper;
     private final PackageManagerServiceInjector mInjector;
+    private final BroadcastHelper mBroadcastHelper;
 
     // TODO(b/198166813): remove PMS dependency
     InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
         mPm = pm;
         mInjector = pm.mInjector;
         mAppDataHelper = appDataHelper;
+        mBroadcastHelper = new BroadcastHelper(mInjector);
     }
 
     InstallPackageHelper(PackageManagerService pm) {
@@ -98,6 +213,7 @@
         mPm = pm;
         mInjector = injector;
         mAppDataHelper = new AppDataHelper(pm, mInjector);
+        mBroadcastHelper = new BroadcastHelper(injector);
     }
 
     @GuardedBy("mPm.mLock")
@@ -700,9 +816,7 @@
                     mPm.mPermissionManager.onPackageInstalled(pkgSetting.getPkg(),
                             Process.INVALID_UID /* previousAppId */,
                             permissionParamsBuilder.build(), userId);
-                }
 
-                if (pkgSetting.getPkg() != null) {
                     synchronized (mPm.mInstallLock) {
                         // We don't need to freeze for a brand new install
                         mAppDataHelper.prepareAppDataAfterInstallLIF(pkgSetting.getPkg());
@@ -882,4 +996,2590 @@
         }
         return false;
     }
+
+    public void processInstallRequests(boolean success, List<InstallRequest> installRequests) {
+        List<InstallRequest> apexInstallRequests = new ArrayList<>();
+        List<InstallRequest> apkInstallRequests = new ArrayList<>();
+        for (InstallRequest request : installRequests) {
+            if ((request.mArgs.mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
+                apexInstallRequests.add(request);
+            } else {
+                apkInstallRequests.add(request);
+            }
+        }
+        // Note: supporting multi package install of both APEXes and APKs might requir some
+        // thinking to ensure atomicity of the install.
+        if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) {
+            // This should've been caught at the validation step, but for some reason wasn't.
+            throw new IllegalStateException(
+                    "Attempted to do a multi package install of both APEXes and APKs");
+        }
+        if (!apexInstallRequests.isEmpty()) {
+            if (success) {
+                // Since installApexPackages requires talking to external service (apexd), we
+                // schedule to run it async. Once it finishes, it will resume the install.
+                Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),
+                        "installApexPackages");
+                t.start();
+            } else {
+                // Non-staged APEX installation failed somewhere before
+                // processInstallRequestAsync. In that case just notify the observer about the
+                // failure.
+                InstallRequest request = apexInstallRequests.get(0);
+                mPm.notifyInstallObserver(request.mInstallResult,
+                        request.mArgs.mObserver);
+            }
+            return;
+        }
+        if (success) {
+            for (InstallRequest request : apkInstallRequests) {
+                request.mArgs.doPreInstall(request.mInstallResult.mReturnCode);
+            }
+            synchronized (mPm.mInstallLock) {
+                installPackagesTracedLI(apkInstallRequests);
+            }
+            for (InstallRequest request : apkInstallRequests) {
+                request.mArgs.doPostInstall(
+                        request.mInstallResult.mReturnCode, request.mInstallResult.mUid);
+            }
+        }
+        for (InstallRequest request : apkInstallRequests) {
+            restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
+                    request.mInstallResult,
+                    new PostInstallData(request.mArgs,
+                            request.mInstallResult, null));
+        }
+    }
+
+    private void installApexPackagesTraced(List<InstallRequest> requests) {
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installApexPackages");
+            installApexPackages(requests);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    private void installApexPackages(List<InstallRequest> requests) {
+        if (requests.isEmpty()) {
+            return;
+        }
+        if (requests.size() != 1) {
+            throw new IllegalStateException(
+                    "Only a non-staged install of a single APEX is supported");
+        }
+        InstallRequest request = requests.get(0);
+        try {
+            // Should directory scanning logic be moved to ApexManager for better test coverage?
+            final File dir = request.mArgs.mOriginInfo.mResolvedFile;
+            final File[] apexes = dir.listFiles();
+            if (apexes == null) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        dir.getAbsolutePath() + " is not a directory");
+            }
+            if (apexes.length != 1) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Expected exactly one .apex file under " + dir.getAbsolutePath()
+                                + " got: " + apexes.length);
+            }
+            try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
+                mPm.mApexManager.installPackage(apexes[0], packageParser);
+            }
+        } catch (PackageManagerException e) {
+            request.mInstallResult.setError("APEX installation failed", e);
+        }
+        PackageManagerService.invalidatePackageInfoCache();
+        mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver);
+    }
+
+    @GuardedBy("mInstallLock")
+    private void installPackagesTracedLI(List<InstallRequest> requests) {
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
+            installPackagesLI(requests);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    /**
+     * Installs one or more packages atomically. This operation is broken up into four phases:
+     * <ul>
+     *     <li><b>Prepare</b>
+     *         <br/>Analyzes any current install state, parses the package and does initial
+     *         validation on it.</li>
+     *     <li><b>Scan</b>
+     *         <br/>Interrogates the parsed packages given the context collected in prepare.</li>
+     *     <li><b>Reconcile</b>
+     *         <br/>Validates scanned packages in the context of each other and the current system
+     *         state to ensure that the install will be successful.
+     *     <li><b>Commit</b>
+     *         <br/>Commits all scanned packages and updates system state. This is the only place
+     *         that system state may be modified in the install flow and all predictable errors
+     *         must be determined before this phase.</li>
+     * </ul>
+     *
+     * Failure at any phase will result in a full failure to install all packages.
+     */
+    @GuardedBy("mInstallLock")
+    private void installPackagesLI(List<InstallRequest> requests) {
+        final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
+        final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size());
+        final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size());
+        final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
+        final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
+        final Map<String, PackageSetting> lastStaticSharedLibSettings =
+                new ArrayMap<>(requests.size());
+        final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
+        boolean success = false;
+        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mPm);
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
+            for (InstallRequest request : requests) {
+                // TODO(b/109941548): remove this once we've pulled everything from it and into
+                //                    scan, reconcile or commit.
+                final PrepareResult prepareResult;
+                try {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
+                    prepareResult =
+                            preparePackageLI(request.mArgs, request.mInstallResult);
+                } catch (PrepareFailure prepareFailure) {
+                    request.mInstallResult.setError(prepareFailure.error,
+                            prepareFailure.getMessage());
+                    request.mInstallResult.mOrigPackage = prepareFailure.mConflictingPackage;
+                    request.mInstallResult.mOrigPermission = prepareFailure.mConflictingPermission;
+                    return;
+                } finally {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+                request.mInstallResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+                request.mInstallResult.mInstallerPackageName =
+                        request.mArgs.mInstallSource.installerPackageName;
+
+                final String packageName = prepareResult.mPackageToScan.getPackageName();
+                prepareResults.put(packageName, prepareResult);
+                installResults.put(packageName, request.mInstallResult);
+                installArgs.put(packageName, request.mArgs);
+                try {
+                    final ScanResult result = scanPackageHelper.scanPackageTracedLI(
+                            prepareResult.mPackageToScan, prepareResult.mParseFlags,
+                            prepareResult.mScanFlags, System.currentTimeMillis(),
+                            request.mArgs.mUser, request.mArgs.mAbiOverride);
+                    if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(),
+                            result)) {
+                        request.mInstallResult.setError(
+                                PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
+                                "Duplicate package "
+                                        + result.mPkgSetting.getPkg().getPackageName()
+                                        + " in multi-package install request.");
+                        return;
+                    }
+                    createdAppId.put(packageName,
+                            scanPackageHelper.optimisticallyRegisterAppId(result));
+                    versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
+                            mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
+                    if (result.mStaticSharedLibraryInfo != null) {
+                        final PackageSetting sharedLibLatestVersionSetting =
+                                mPm.getSharedLibLatestVersionSetting(result);
+                        if (sharedLibLatestVersionSetting != null) {
+                            lastStaticSharedLibSettings.put(
+                                    result.mPkgSetting.getPkg().getPackageName(),
+                                    sharedLibLatestVersionSetting);
+                        }
+                    }
+                } catch (PackageManagerException e) {
+                    request.mInstallResult.setError("Scanning Failed.", e);
+                    return;
+                }
+            }
+            ReconcileRequest
+                    reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
+                    installResults,
+                    prepareResults,
+                    mPm.mSharedLibraries,
+                    Collections.unmodifiableMap(mPm.mPackages), versionInfos,
+                    lastStaticSharedLibSettings);
+            CommitRequest commitRequest = null;
+            synchronized (mPm.mLock) {
+                Map<String, ReconciledPackage> reconciledPackages;
+                try {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
+                    reconciledPackages = reconcilePackagesLocked(
+                            reconcileRequest, mPm.mSettings.getKeySetManagerService(),
+                            mPm.mInjector);
+                } catch (ReconcileFailure e) {
+                    for (InstallRequest request : requests) {
+                        request.mInstallResult.setError("Reconciliation failed...", e);
+                    }
+                    return;
+                } finally {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+                try {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
+                    commitRequest = new CommitRequest(reconciledPackages,
+                            mPm.mUserManager.getUserIds());
+                    commitPackagesLocked(commitRequest);
+                    success = true;
+                } finally {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+            }
+            executePostCommitSteps(commitRequest);
+        } finally {
+            if (success) {
+                for (InstallRequest request : requests) {
+                    final InstallArgs args = request.mArgs;
+                    if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) {
+                        continue;
+                    }
+                    if (args.mSigningDetails.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) {
+                        continue;
+                    }
+                    // For incremental installs, we bypass the verifier prior to install. Now
+                    // that we know the package is valid, send a notice to the verifier with
+                    // the root hash of the base.apk.
+                    final String baseCodePath = request.mInstallResult.mPkg.getBaseApkPath();
+                    final String[] splitCodePaths = request.mInstallResult.mPkg.getSplitCodePaths();
+                    final Uri originUri = Uri.fromFile(args.mOriginInfo.mResolvedFile);
+                    final int verificationId = mPm.mPendingVerificationToken++;
+                    final String rootHashString = PackageManagerServiceUtils
+                            .buildVerificationRootHashString(baseCodePath, splitCodePaths);
+                    VerificationUtils.broadcastPackageVerified(verificationId, originUri,
+                            PackageManager.VERIFICATION_ALLOW, rootHashString,
+                            args.mDataLoaderType, args.getUser(), mPm.mContext);
+                }
+            } else {
+                for (ScanResult result : preparedScans.values()) {
+                    if (createdAppId.getOrDefault(result.mRequest.mParsedPackage.getPackageName(),
+                            false)) {
+                        scanPackageHelper.cleanUpAppIdCreation(result);
+                    }
+                }
+                // TODO(b/194319951): create a more descriptive reason than unknown
+                // mark all non-failure installs as UNKNOWN so we do not treat them as success
+                for (InstallRequest request : requests) {
+                    if (request.mInstallResult.mFreezer != null) {
+                        request.mInstallResult.mFreezer.close();
+                    }
+                    if (request.mInstallResult.mReturnCode == PackageManager.INSTALL_SUCCEEDED) {
+                        request.mInstallResult.mReturnCode = PackageManager.INSTALL_UNKNOWN;
+                    }
+                }
+            }
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    @GuardedBy("mInstallLock")
+    private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
+            throws PrepareFailure {
+        final int installFlags = args.mInstallFlags;
+        final File tmpPackageFile = new File(args.getCodePath());
+        final boolean onExternal = args.mVolumeUuid != null;
+        final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
+        final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
+        final boolean virtualPreload =
+                ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+        final boolean isRollback = args.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+        @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
+        if (args.mMoveInfo != null) {
+            // moving a complete application; perform an initial scan on the new install location
+            scanFlags |= SCAN_INITIAL;
+        }
+        if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
+            scanFlags |= SCAN_DONT_KILL_APP;
+        }
+        if (instantApp) {
+            scanFlags |= SCAN_AS_INSTANT_APP;
+        }
+        if (fullApp) {
+            scanFlags |= SCAN_AS_FULL_APP;
+        }
+        if (virtualPreload) {
+            scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
+        }
+
+        if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
+
+        // Validity check
+        if (instantApp && onExternal) {
+            Slog.i(TAG, "Incompatible ephemeral install; external=" + onExternal);
+            throw new PrepareFailure(PackageManager.INSTALL_FAILED_SESSION_INVALID);
+        }
+
+        // Retrieve PackageSettings and parse package
+        @ParsingPackageUtils.ParseFlags final int parseFlags =
+                mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
+                        | ParsingPackageUtils.PARSE_ENFORCE_CODE
+                        | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
+
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
+        final ParsedPackage parsedPackage;
+        try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
+            parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
+            AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+        } catch (PackageManagerException e) {
+            throw new PrepareFailure("Failed parse during installPackageLI", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+
+        // Instant apps have several additional install-time checks.
+        if (instantApp) {
+            if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) {
+                Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
+                        + " does not target at least O");
+                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
+                        "Instant app package must target at least O");
+            }
+            if (parsedPackage.getSharedUserId() != null) {
+                Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
+                        + " may not declare sharedUserId.");
+                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
+                        "Instant app package may not declare a sharedUserId");
+            }
+        }
+
+        if (parsedPackage.isStaticSharedLibrary()) {
+            // Static shared libraries have synthetic package names
+            PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
+
+            // No static shared libs on external storage
+            if (onExternal) {
+                Slog.i(TAG, "Static shared libs can only be installed on internal storage.");
+                throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+                        "Packages declaring static-shared libs cannot be updated");
+            }
+        }
+
+        String pkgName = res.mName = parsedPackage.getPackageName();
+        if (parsedPackage.isTestOnly()) {
+            if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
+                throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
+            }
+        }
+
+        // either use what we've been given or parse directly from the APK
+        if (args.mSigningDetails != SigningDetails.UNKNOWN) {
+            parsedPackage.setSigningDetails(args.mSigningDetails);
+        } else {
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
+                    input, parsedPackage, false /*skipVerify*/);
+            if (result.isError()) {
+                throw new PrepareFailure("Failed collect during installPackageLI",
+                        result.getException());
+            }
+            parsedPackage.setSigningDetails(result.getResult());
+        }
+
+        if (instantApp && parsedPackage.getSigningDetails().getSignatureSchemeVersion()
+                < SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2) {
+            Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
+                    + " is not signed with at least APK Signature Scheme v2");
+            throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
+                    "Instant app package must be signed with APK Signature Scheme v2 or greater");
+        }
+
+        boolean systemApp = false;
+        boolean replace = false;
+        synchronized (mPm.mLock) {
+            // Check if installing already existing package
+            if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
+                String oldName = mPm.mSettings.getRenamedPackageLPr(pkgName);
+                if (parsedPackage.getOriginalPackages().contains(oldName)
+                        && mPm.mPackages.containsKey(oldName)) {
+                    // This package is derived from an original package,
+                    // and this device has been updating from that original
+                    // name.  We must continue using the original name, so
+                    // rename the new package here.
+                    parsedPackage.setPackageName(oldName);
+                    pkgName = parsedPackage.getPackageName();
+                    replace = true;
+                    if (DEBUG_INSTALL) {
+                        Slog.d(TAG, "Replacing existing renamed package: oldName="
+                                + oldName + " pkgName=" + pkgName);
+                    }
+                } else if (mPm.mPackages.containsKey(pkgName)) {
+                    // This package, under its official name, already exists
+                    // on the device; we should replace it.
+                    replace = true;
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);
+                }
+
+                if (replace) {
+                    // Prevent apps opting out from runtime permissions
+                    AndroidPackage oldPackage = mPm.mPackages.get(pkgName);
+                    final int oldTargetSdk = oldPackage.getTargetSdkVersion();
+                    final int newTargetSdk = parsedPackage.getTargetSdkVersion();
+                    if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
+                            && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+                        throw new PrepareFailure(
+                                PackageManager.INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " new target SDK " + newTargetSdk
+                                        + " doesn't support runtime permissions but the old"
+                                        + " target SDK " + oldTargetSdk + " does.");
+                    }
+                    // Prevent persistent apps from being updated
+                    if (oldPackage.isPersistent()
+                            && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
+                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
+                                "Package " + oldPackage.getPackageName() + " is a persistent app. "
+                                        + "Persistent apps are not updateable.");
+                    }
+                }
+            }
+
+            PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
+            if (ps != null) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
+
+                // Static shared libs have same package with different versions where
+                // we internally use a synthetic package name to allow multiple versions
+                // of the same package, therefore we need to compare signatures against
+                // the package setting for the latest library version.
+                PackageSetting signatureCheckPs = ps;
+                if (parsedPackage.isStaticSharedLibrary()) {
+                    SharedLibraryInfo libraryInfo = mPm.getLatestSharedLibraVersionLPr(
+                            parsedPackage);
+                    if (libraryInfo != null) {
+                        signatureCheckPs = mPm.mSettings.getPackageLPr(
+                                libraryInfo.getPackageName());
+                    }
+                }
+
+                // Quick validity check that we're signed correctly if updating;
+                // we'll check this again later when scanning, but we want to
+                // bail early here before tripping over redefined permissions.
+                final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+                if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                    if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
+                        throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+                                + parsedPackage.getPackageName() + " upgrade keys do not match the "
+                                + "previously installed version");
+                    }
+                } else {
+                    try {
+                        final boolean compareCompat =
+                                isCompatSignatureUpdateNeeded(parsedPackage);
+                        final boolean compareRecover =
+                                isRecoverSignatureUpdateNeeded(parsedPackage);
+                        // We don't care about disabledPkgSetting on install for now.
+                        final boolean compatMatch = verifySignatures(signatureCheckPs, null,
+                                parsedPackage.getSigningDetails(), compareCompat, compareRecover,
+                                isRollback);
+                        // The new KeySets will be re-added later in the scanning process.
+                        if (compatMatch) {
+                            synchronized (mPm.mLock) {
+                                ksms.removeAppKeySetDataLPw(parsedPackage.getPackageName());
+                            }
+                        }
+                    } catch (PackageManagerException e) {
+                        throw new PrepareFailure(e.error, e.getMessage());
+                    }
+                }
+
+                if (ps.getPkg() != null) {
+                    systemApp = ps.getPkg().isSystem();
+                }
+                res.mOrigUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true);
+            }
+
+            final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups());
+            for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+                final ParsedPermissionGroup group =
+                        parsedPackage.getPermissionGroups().get(groupNum);
+                final PermissionGroupInfo sourceGroup = mPm.getPermissionGroupInfo(group.getName(),
+                        0);
+
+                if (sourceGroup != null && cannotInstallWithBadPermissionGroups(parsedPackage)) {
+                    final String sourcePackageName = sourceGroup.packageName;
+
+                    if ((replace || !parsedPackage.getPackageName().equals(sourcePackageName))
+                            && !doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
+                            scanFlags)) {
+                        EventLog.writeEvent(0x534e4554, "146211400", -1,
+                                parsedPackage.getPackageName());
+
+                        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP,
+                                "Package "
+                                        + parsedPackage.getPackageName()
+                                        + " attempting to redeclare permission group "
+                                        + group.getName() + " already owned by "
+                                        + sourcePackageName);
+                    }
+                }
+            }
+
+            // TODO: Move logic for checking permission compatibility into PermissionManagerService
+            final int n = ArrayUtils.size(parsedPackage.getPermissions());
+            for (int i = n - 1; i >= 0; i--) {
+                final ParsedPermission perm = parsedPackage.getPermissions().get(i);
+                final Permission bp = mPm.mPermissionManager.getPermissionTEMP(perm.getName());
+
+                // Don't allow anyone but the system to define ephemeral permissions.
+                if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
+                        && !systemApp) {
+                    Slog.w(TAG, "Non-System package " + parsedPackage.getPackageName()
+                            + " attempting to delcare ephemeral permission "
+                            + perm.getName() + "; Removing ephemeral.");
+                    ComponentMutateUtils.setProtectionLevel(perm,
+                            perm.getProtectionLevel() & ~PermissionInfo.PROTECTION_FLAG_INSTANT);
+                }
+
+                // Check whether the newly-scanned package wants to define an already-defined perm
+                if (bp != null) {
+                    final String sourcePackageName = bp.getPackageName();
+
+                    if (!doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
+                            scanFlags)) {
+                        // If the owning package is the system itself, we log but allow
+                        // install to proceed; we fail the install on all other permission
+                        // redefinitions.
+                        if (!sourcePackageName.equals("android")) {
+                            throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION,
+                                    "Package "
+                                            + parsedPackage.getPackageName()
+                                            + " attempting to redeclare permission "
+                                            + perm.getName() + " already owned by "
+                                            + sourcePackageName)
+                                    .conflictsWithExistingPermission(perm.getName(),
+                                            sourcePackageName);
+                        } else {
+                            Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+                                    + " attempting to redeclare system permission "
+                                    + perm.getName() + "; ignoring new declaration");
+                            parsedPackage.removePermission(i);
+                        }
+                    } else if (!PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())) {
+                        // Prevent apps to change protection level to dangerous from any other
+                        // type as this would allow a privilege escalation where an app adds a
+                        // normal/signature permission in other app's group and later redefines
+                        // it as dangerous leading to the group auto-grant.
+                        if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE)
+                                == PermissionInfo.PROTECTION_DANGEROUS) {
+                            if (bp != null && !bp.isRuntime()) {
+                                Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+                                        + " trying to change a non-runtime permission "
+                                        + perm.getName()
+                                        + " to runtime; keeping old protection level");
+                                ComponentMutateUtils.setProtectionLevel(perm,
+                                        bp.getProtectionLevel());
+                            }
+                        }
+                    }
+                }
+
+                if (perm.getGroup() != null
+                        && cannotInstallWithBadPermissionGroups(parsedPackage)) {
+                    boolean isPermGroupDefinedByPackage = false;
+                    for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+                        if (parsedPackage.getPermissionGroups().get(groupNum).getName()
+                                .equals(perm.getGroup())) {
+                            isPermGroupDefinedByPackage = true;
+                            break;
+                        }
+                    }
+
+                    if (!isPermGroupDefinedByPackage) {
+                        final PermissionGroupInfo sourceGroup =
+                                mPm.getPermissionGroupInfo(perm.getGroup(), 0);
+
+                        if (sourceGroup == null) {
+                            EventLog.writeEvent(0x534e4554, "146211400", -1,
+                                    parsedPackage.getPackageName());
+
+                            throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP,
+                                    "Package "
+                                            + parsedPackage.getPackageName()
+                                            + " attempting to declare permission "
+                                            + perm.getName() + " in non-existing group "
+                                            + perm.getGroup());
+                        } else {
+                            String groupSourcePackageName = sourceGroup.packageName;
+
+                            if (!PLATFORM_PACKAGE_NAME.equals(groupSourcePackageName)
+                                    && !doesSignatureMatchForPermissions(groupSourcePackageName,
+                                    parsedPackage, scanFlags)) {
+                                EventLog.writeEvent(0x534e4554, "146211400", -1,
+                                        parsedPackage.getPackageName());
+
+                                throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP,
+                                        "Package "
+                                                + parsedPackage.getPackageName()
+                                                + " attempting to declare permission "
+                                                + perm.getName() + " in group "
+                                                + perm.getGroup() + " owned by package "
+                                                + groupSourcePackageName
+                                                + " with incompatible certificate");
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (systemApp) {
+            if (onExternal) {
+                // Abort update; system app can't be replaced with app on sdcard
+                throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+                        "Cannot install updates to system apps on sdcard");
+            } else if (instantApp) {
+                // Abort update; system app can't be replaced with an instant app
+                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
+                        "Cannot update a system app with an instant app");
+            }
+        }
+
+        if (args.mMoveInfo != null) {
+            // We did an in-place move, so dex is ready to roll
+            scanFlags |= SCAN_NO_DEX;
+            scanFlags |= SCAN_MOVE;
+
+            synchronized (mPm.mLock) {
+                final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
+                if (ps == null) {
+                    res.setError(INSTALL_FAILED_INTERNAL_ERROR,
+                            "Missing settings for moved package " + pkgName);
+                }
+
+                // We moved the entire application as-is, so bring over the
+                // previously derived ABI information.
+                parsedPackage.setPrimaryCpuAbi(ps.getPrimaryCpuAbi())
+                        .setSecondaryCpuAbi(ps.getSecondaryCpuAbi());
+            }
+
+        } else {
+            // Enable SCAN_NO_DEX flag to skip dexopt at a later stage
+            scanFlags |= SCAN_NO_DEX;
+
+            try {
+                PackageSetting pkgSetting;
+                AndroidPackage oldPackage;
+                synchronized (mPm.mLock) {
+                    pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
+                    oldPackage = mPm.mPackages.get(pkgName);
+                }
+                boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
+                        && pkgSetting.getPkgState().isUpdatedSystemApp();
+                final String abiOverride = deriveAbiOverride(args.mAbiOverride);
+                boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
+                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
+                        derivedAbi = mPm.mInjector.getAbiHelper().derivePackageAbi(parsedPackage,
+                        isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
+                        abiOverride, mPm.mAppLib32InstallDir);
+                derivedAbi.first.applyTo(parsedPackage);
+                derivedAbi.second.applyTo(parsedPackage);
+            } catch (PackageManagerException pme) {
+                Slog.e(TAG, "Error deriving application ABI", pme);
+                throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Error deriving application ABI: " + pme.getMessage());
+            }
+        }
+
+        if (!args.doRename(res.mReturnCode, parsedPackage)) {
+            throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
+        }
+
+        try {
+            setUpFsVerityIfPossible(parsedPackage);
+        } catch (Installer.InstallerException | IOException | DigestException
+                | NoSuchAlgorithmException e) {
+            throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
+                    "Failed to set up verity: " + e);
+        }
+
+        final PackageFreezer freezer =
+                freezePackageForInstall(pkgName, installFlags, "installPackageLI");
+        boolean shouldCloseFreezerBeforeReturn = true;
+        try {
+            final AndroidPackage oldPackage;
+            String renamedPackage;
+            boolean sysPkg = false;
+            int targetScanFlags = scanFlags;
+            int targetParseFlags = parseFlags;
+            final PackageSetting ps;
+            final PackageSetting disabledPs;
+            if (replace) {
+                final String pkgName11 = parsedPackage.getPackageName();
+                synchronized (mPm.mLock) {
+                    oldPackage = mPm.mPackages.get(pkgName11);
+                }
+                if (parsedPackage.isStaticSharedLibrary()) {
+                    // Static libs have a synthetic package name containing the version
+                    // and cannot be updated as an update would get a new package name,
+                    // unless this is installed from adb which is useful for development.
+                    if (oldPackage != null
+                            && (installFlags & PackageManager.INSTALL_FROM_ADB) == 0) {
+                        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                                "Packages declaring "
+                                        + "static-shared libs cannot be updated");
+                    }
+                }
+
+                final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
+
+                final int[] allUsers;
+                final int[] installedUsers;
+                final int[] uninstalledUsers;
+
+                synchronized (mPm.mLock) {
+                    if (DEBUG_INSTALL) {
+                        Slog.d(TAG,
+                                "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage);
+                    }
+
+                    ps = mPm.mSettings.getPackageLPr(pkgName11);
+                    disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps);
+
+                    // verify signatures are valid
+                    final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+                    if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) {
+                        if (!ksms.checkUpgradeKeySetLocked(ps, parsedPackage)) {
+                            throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                    "New package not signed by keys specified by upgrade-keysets: "
+                                            + pkgName11);
+                        }
+                    } else {
+                        SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails();
+                        SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails();
+                        // default to original signature matching
+                        if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails,
+                                SigningDetails.CertCapabilities.INSTALLED_DATA)
+                                && !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails,
+                                SigningDetails.CertCapabilities.ROLLBACK)) {
+                            // Allow the update to proceed if this is a rollback and the parsed
+                            // package's current signing key is the current signer or in the lineage
+                            // of the old package; this allows a rollback to a previously installed
+                            // version after an app's signing key has been rotated without requiring
+                            // the rollback capability on the previous signing key.
+                            if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
+                                    parsedPkgSigningDetails)) {
+                                throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                        "New package has a different signature: " + pkgName11);
+                            }
+                        }
+                    }
+
+                    // don't allow a system upgrade unless the upgrade hash matches
+                    if (oldPackage.getRestrictUpdateHash() != null && oldPackage.isSystem()) {
+                        final byte[] digestBytes;
+                        try {
+                            final MessageDigest digest = MessageDigest.getInstance("SHA-512");
+                            updateDigest(digest, new File(parsedPackage.getBaseApkPath()));
+                            if (!ArrayUtils.isEmpty(parsedPackage.getSplitCodePaths())) {
+                                for (String path : parsedPackage.getSplitCodePaths()) {
+                                    updateDigest(digest, new File(path));
+                                }
+                            }
+                            digestBytes = digest.digest();
+                        } catch (NoSuchAlgorithmException | IOException e) {
+                            throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
+                                    "Could not compute hash: " + pkgName11);
+                        }
+                        if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
+                            throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
+                                    "New package fails restrict-update check: " + pkgName11);
+                        }
+                        // retain upgrade restriction
+                        parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
+                    }
+
+                    // Check for shared user id changes
+                    if (!Objects.equals(oldPackage.getSharedUserId(),
+                            parsedPackage.getSharedUserId())
+                            // Don't mark as invalid if the app is trying to
+                            // leave a sharedUserId
+                            && parsedPackage.getSharedUserId() != null) {
+                        throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " shared user changed from "
+                                        + (oldPackage.getSharedUserId() != null
+                                        ? oldPackage.getSharedUserId() : "<nothing>")
+                                        + " to " + parsedPackage.getSharedUserId());
+                    }
+
+                    // In case of rollback, remember per-user/profile install state
+                    allUsers = mPm.mUserManager.getUserIds();
+                    installedUsers = ps.queryInstalledUsers(allUsers, true);
+                    uninstalledUsers = ps.queryInstalledUsers(allUsers, false);
+
+
+                    // don't allow an upgrade from full to ephemeral
+                    if (isInstantApp) {
+                        if (args.mUser == null
+                                || args.mUser.getIdentifier() == UserHandle.USER_ALL) {
+                            for (int currentUser : allUsers) {
+                                if (!ps.getInstantApp(currentUser)) {
+                                    // can't downgrade from full to instant
+                                    Slog.w(TAG,
+                                            "Can't replace full app with instant app: " + pkgName11
+                                                    + " for user: " + currentUser);
+                                    throw new PrepareFailure(
+                                            PackageManager.INSTALL_FAILED_SESSION_INVALID);
+                                }
+                            }
+                        } else if (!ps.getInstantApp(args.mUser.getIdentifier())) {
+                            // can't downgrade from full to instant
+                            Slog.w(TAG, "Can't replace full app with instant app: " + pkgName11
+                                    + " for user: " + args.mUser.getIdentifier());
+                            throw new PrepareFailure(
+                                    PackageManager.INSTALL_FAILED_SESSION_INVALID);
+                        }
+                    }
+                }
+
+                // Update what is removed
+                res.mRemovedInfo = new PackageRemovedInfo(mPm);
+                res.mRemovedInfo.mUid = oldPackage.getUid();
+                res.mRemovedInfo.mRemovedPackage = oldPackage.getPackageName();
+                res.mRemovedInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName;
+                res.mRemovedInfo.mIsStaticSharedLib =
+                        parsedPackage.getStaticSharedLibName() != null;
+                res.mRemovedInfo.mIsUpdate = true;
+                res.mRemovedInfo.mOrigUsers = installedUsers;
+                res.mRemovedInfo.mInstallReasons = new SparseArray<>(installedUsers.length);
+                for (int i = 0; i < installedUsers.length; i++) {
+                    final int userId = installedUsers[i];
+                    res.mRemovedInfo.mInstallReasons.put(userId, ps.getInstallReason(userId));
+                }
+                res.mRemovedInfo.mUninstallReasons = new SparseArray<>(uninstalledUsers.length);
+                for (int i = 0; i < uninstalledUsers.length; i++) {
+                    final int userId = uninstalledUsers[i];
+                    res.mRemovedInfo.mUninstallReasons.put(userId, ps.getUninstallReason(userId));
+                }
+
+                sysPkg = oldPackage.isSystem();
+                if (sysPkg) {
+                    // Set the system/privileged/oem/vendor/product flags as needed
+                    final boolean privileged = oldPackage.isPrivileged();
+                    final boolean oem = oldPackage.isOem();
+                    final boolean vendor = oldPackage.isVendor();
+                    final boolean product = oldPackage.isProduct();
+                    final boolean odm = oldPackage.isOdm();
+                    final boolean systemExt = oldPackage.isSystemExt();
+                    final @ParsingPackageUtils.ParseFlags int systemParseFlags = parseFlags;
+                    final @PackageManagerService.ScanFlags int systemScanFlags = scanFlags
+                            | SCAN_AS_SYSTEM
+                            | (privileged ? SCAN_AS_PRIVILEGED : 0)
+                            | (oem ? SCAN_AS_OEM : 0)
+                            | (vendor ? SCAN_AS_VENDOR : 0)
+                            | (product ? SCAN_AS_PRODUCT : 0)
+                            | (odm ? SCAN_AS_ODM : 0)
+                            | (systemExt ? SCAN_AS_SYSTEM_EXT : 0);
+
+                    if (DEBUG_INSTALL) {
+                        Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
+                                + ", old=" + oldPackage);
+                    }
+                    res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+                    targetParseFlags = systemParseFlags;
+                    targetScanFlags = systemScanFlags;
+                } else { // non system replace
+                    replace = true;
+                    if (DEBUG_INSTALL) {
+                        Slog.d(TAG,
+                                "replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
+                                        + oldPackage);
+                    }
+                }
+            } else { // new package install
+                ps = null;
+                disabledPs = null;
+                replace = false;
+                oldPackage = null;
+                // Remember this for later, in case we need to rollback this install
+                String pkgName1 = parsedPackage.getPackageName();
+
+                if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + parsedPackage);
+
+                // TODO(b/194319951): MOVE TO RECONCILE
+                synchronized (mPm.mLock) {
+                    renamedPackage = mPm.mSettings.getRenamedPackageLPr(pkgName1);
+                    if (renamedPackage != null) {
+                        // A package with the same name is already installed, though
+                        // it has been renamed to an older name.  The package we
+                        // are trying to install should be installed as an update to
+                        // the existing one, but that has not been requested, so bail.
+                        throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS,
+                                "Attempt to re-install " + pkgName1
+                                        + " without first uninstalling package running as "
+                                        + renamedPackage);
+                    }
+                    if (mPm.mPackages.containsKey(pkgName1)) {
+                        // Don't allow installation over an existing package with the same name.
+                        throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS,
+                                "Attempt to re-install " + pkgName1
+                                        + " without first uninstalling.");
+                    }
+                }
+            }
+            // we're passing the freezer back to be closed in a later phase of install
+            shouldCloseFreezerBeforeReturn = false;
+
+            return new PrepareResult(replace, targetScanFlags, targetParseFlags,
+                    oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg,
+                    ps, disabledPs);
+        } finally {
+            res.mFreezer = freezer;
+            if (shouldCloseFreezerBeforeReturn) {
+                freezer.close();
+            }
+        }
+    }
+
+    /*
+     * Cannot properly check CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS using CompatChanges
+     * as this only works for packages that are installed
+     *
+     * TODO: Move logic for permission group compatibility into PermissionManagerService
+     */
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private static boolean cannotInstallWithBadPermissionGroups(ParsedPackage parsedPackage) {
+        return parsedPackage.getTargetSdkVersion() >= Build.VERSION_CODES.S;
+    }
+
+    private boolean doesSignatureMatchForPermissions(@NonNull String sourcePackageName,
+            @NonNull ParsedPackage parsedPackage, int scanFlags) {
+        // If the defining package is signed with our cert, it's okay.  This
+        // also includes the "updating the same package" case, of course.
+        // "updating same package" could also involve key-rotation.
+
+        final PackageSetting sourcePackageSetting;
+        final KeySetManagerService ksms;
+        synchronized (mPm.mLock) {
+            sourcePackageSetting = mPm.mSettings.getPackageLPr(sourcePackageName);
+            ksms = mPm.mSettings.getKeySetManagerService();
+        }
+
+        final SigningDetails sourceSigningDetails = (sourcePackageSetting == null
+                ? SigningDetails.UNKNOWN : sourcePackageSetting.getSigningDetails());
+        if (sourcePackageName.equals(parsedPackage.getPackageName())
+                && (ksms.shouldCheckUpgradeKeySetLocked(
+                sourcePackageSetting, scanFlags))) {
+            return ksms.checkUpgradeKeySetLocked(sourcePackageSetting, parsedPackage);
+        } else {
+
+            // in the event of signing certificate rotation, we need to see if the
+            // package's certificate has rotated from the current one, or if it is an
+            // older certificate with which the current is ok with sharing permissions
+            if (sourceSigningDetails.checkCapability(
+                    parsedPackage.getSigningDetails(),
+                    SigningDetails.CertCapabilities.PERMISSION)) {
+                return true;
+            } else if (parsedPackage.getSigningDetails().checkCapability(
+                    sourceSigningDetails,
+                    SigningDetails.CertCapabilities.PERMISSION)) {
+                // the scanned package checks out, has signing certificate rotation
+                // history, and is newer; bring it over
+                synchronized (mPm.mLock) {
+                    sourcePackageSetting.setSigningDetails(parsedPackage.getSigningDetails());
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set up fs-verity for the given package if possible.  This requires a feature flag of system
+     * property to be enabled only if the kernel supports fs-verity.
+     *
+     * <p>When the feature flag is set to legacy mode, only APK is supported (with some experimental
+     * kernel patches). In normal mode, all file format can be supported.
+     */
+    private void setUpFsVerityIfPossible(AndroidPackage pkg) throws Installer.InstallerException,
+            PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
+        final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
+        final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
+        if (!standardMode && !legacyMode) {
+            return;
+        }
+
+        if (isIncrementalPath(pkg.getPath()) && IncrementalManager.getVersion()
+                < IncrementalManager.MIN_VERSION_TO_SUPPORT_FSVERITY) {
+            return;
+        }
+
+        // Collect files we care for fs-verity setup.
+        ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
+        if (legacyMode) {
+            synchronized (mPm.mLock) {
+                final PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
+                if (ps != null && ps.isPrivileged()) {
+                    fsverityCandidates.put(pkg.getBaseApkPath(), null);
+                    if (pkg.getSplitCodePaths() != null) {
+                        for (String splitPath : pkg.getSplitCodePaths()) {
+                            fsverityCandidates.put(splitPath, null);
+                        }
+                    }
+                }
+            }
+        } else {
+            // NB: These files will become only accessible if the signing key is loaded in kernel's
+            // .fs-verity keyring.
+            fsverityCandidates.put(pkg.getBaseApkPath(),
+                    VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
+
+            final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
+                    pkg.getBaseApkPath());
+            if (new File(dmPath).exists()) {
+                fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
+            }
+
+            if (pkg.getSplitCodePaths() != null) {
+                for (String path : pkg.getSplitCodePaths()) {
+                    fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
+
+                    final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
+                    if (new File(splitDmPath).exists()) {
+                        fsverityCandidates.put(splitDmPath,
+                                VerityUtils.getFsveritySignatureFilePath(splitDmPath));
+                    }
+                }
+            }
+        }
+
+        for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
+            final String filePath = entry.getKey();
+            final String signaturePath = entry.getValue();
+
+            if (!legacyMode) {
+                // fs-verity is optional for now.  Only set up if signature is provided.
+                if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
+                    try {
+                        VerityUtils.setUpFsverity(filePath, signaturePath);
+                    } catch (IOException e) {
+                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+                                "Failed to enable fs-verity: " + e);
+                    }
+                }
+                continue;
+            }
+
+            // In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
+            final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(filePath);
+            if (result.isOk()) {
+                if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling verity to " + filePath);
+                final FileDescriptor fd = result.getUnownedFileDescriptor();
+                try {
+                    final byte[] rootHash = VerityUtils.generateApkVerityRootHash(filePath);
+                    try {
+                        // A file may already have fs-verity, e.g. when reused during a split
+                        // install. If the measurement succeeds, no need to attempt to set up.
+                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+                    } catch (Installer.InstallerException e) {
+                        mPm.mInstaller.installApkVerity(filePath, fd, result.getContentSize());
+                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+                    }
+                } finally {
+                    IoUtils.closeQuietly(fd);
+                }
+            } else if (result.isFailed()) {
+                throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+                        "Failed to generate verity");
+            }
+        }
+    }
+
+    private PackageFreezer freezePackageForInstall(String packageName, int installFlags,
+            String killReason) {
+        return freezePackageForInstall(packageName, UserHandle.USER_ALL, installFlags, killReason);
+    }
+
+    private PackageFreezer freezePackageForInstall(String packageName, int userId, int installFlags,
+            String killReason) {
+        if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
+            return new PackageFreezer(mPm);
+        } else {
+            return mPm.freezePackage(packageName, userId, killReason);
+        }
+    }
+
+    private static void updateDigest(MessageDigest digest, File file) throws IOException {
+        try (DigestInputStream digestStream =
+                     new DigestInputStream(new FileInputStream(file), digest)) {
+            int length, total = 0;
+            while ((length = digestStream.read()) != -1) {
+                total += length;
+            } // just plow through the file
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void commitPackagesLocked(final CommitRequest request) {
+        // TODO: remove any expected failures from this method; this should only be able to fail due
+        //       to unavoidable errors (I/O, etc.)
+        for (ReconciledPackage reconciledPkg : request.mReconciledPackages.values()) {
+            final ScanResult scanResult = reconciledPkg.mScanResult;
+            final ScanRequest scanRequest = scanResult.mRequest;
+            final ParsedPackage parsedPackage = scanRequest.mParsedPackage;
+            final String packageName = parsedPackage.getPackageName();
+            final PackageInstalledInfo res = reconciledPkg.mInstallResult;
+            final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
+            final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
+
+            if (reconciledPkg.mPrepareResult.mReplace) {
+                AndroidPackage oldPackage = mPm.mPackages.get(packageName);
+
+                // Set the update and install times
+                PackageStateInternal deletedPkgSetting = mPm.getPackageStateInternal(
+                        oldPackage.getPackageName());
+                reconciledPkg.mPkgSetting
+                        .setFirstInstallTime(deletedPkgSetting.getFirstInstallTime())
+                        .setLastUpdateTime(System.currentTimeMillis());
+
+                res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
+                        reconciledPkg.mPkgSetting, request.mAllUsers,
+                        mPm.mSettings.getPackagesLocked());
+                if (reconciledPkg.mPrepareResult.mSystem) {
+                    // Remove existing system package
+                    removePackageHelper.removePackageLI(oldPackage, true);
+                    if (!disableSystemPackageLPw(oldPackage)) {
+                        // We didn't need to disable the .apk as a current system package,
+                        // which means we are replacing another update that is already
+                        // installed.  We need to make sure to delete the older one's .apk.
+                        res.mRemovedInfo.mArgs = new FileInstallArgs(
+                                oldPackage.getPath(),
+                                getAppDexInstructionSets(
+                                        AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
+                                                deletedPkgSetting),
+                                        AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,
+                                                deletedPkgSetting)), mPm);
+                    } else {
+                        res.mRemovedInfo.mArgs = null;
+                    }
+                } else {
+                    try {
+                        // Settings will be written during the call to updateSettingsLI().
+                        deletePackageHelper.executeDeletePackageLIF(
+                                reconciledPkg.mDeletePackageAction, packageName,
+                                true, request.mAllUsers, false);
+                    } catch (SystemDeleteException e) {
+                        if (mPm.mIsEngBuild) {
+                            throw new RuntimeException("Unexpected failure", e);
+                            // ignore; not possible for non-system app
+                        }
+                    }
+                    // Successfully deleted the old package; proceed with replace.
+
+                    // If deleted package lived in a container, give users a chance to
+                    // relinquish resources before killing.
+                    if (oldPackage.isExternalStorage()) {
+                        if (DEBUG_INSTALL) {
+                            Slog.i(TAG, "upgrading pkg " + oldPackage
+                                    + " is ASEC-hosted -> UNAVAILABLE");
+                        }
+                        final int[] uidArray = new int[]{oldPackage.getUid()};
+                        final ArrayList<String> pkgList = new ArrayList<>(1);
+                        pkgList.add(oldPackage.getPackageName());
+                        mBroadcastHelper.sendResourcesChangedBroadcast(
+                                false, true, pkgList, uidArray, null);
+                    }
+
+                    // Update the in-memory copy of the previous code paths.
+                    PackageSetting ps1 = mPm.mSettings.getPackageLPr(
+                            reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
+                    if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP)
+                            == 0) {
+                        if (ps1.mOldCodePaths == null) {
+                            ps1.mOldCodePaths = new ArraySet<>();
+                        }
+                        Collections.addAll(ps1.mOldCodePaths, oldPackage.getBaseApkPath());
+                        if (oldPackage.getSplitCodePaths() != null) {
+                            Collections.addAll(ps1.mOldCodePaths, oldPackage.getSplitCodePaths());
+                        }
+                    } else {
+                        ps1.mOldCodePaths = null;
+                    }
+
+                    if (reconciledPkg.mInstallResult.mReturnCode
+                            == PackageManager.INSTALL_SUCCEEDED) {
+                        PackageSetting ps2 = mPm.mSettings.getPackageLPr(
+                                parsedPackage.getPackageName());
+                        if (ps2 != null) {
+                            res.mRemovedInfo.mRemovedForAllUsers =
+                                    mPm.mPackages.get(ps2.getPackageName()) == null;
+                        }
+                    }
+                }
+            }
+
+            AndroidPackage pkg = commitReconciledScanResultLocked(
+                    reconciledPkg, request.mAllUsers);
+            updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res);
+
+            final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps != null) {
+                res.mNewUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true);
+                ps.setUpdateAvailable(false /*updateAvailable*/);
+            }
+            if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED) {
+                mPm.updateSequenceNumberLP(ps, res.mNewUsers);
+                mPm.updateInstantAppInstallerLocked(packageName);
+            }
+        }
+        ApplicationPackageManager.invalidateGetPackagesForUidCache();
+    }
+
+    @GuardedBy("mLock")
+    private boolean disableSystemPackageLPw(AndroidPackage oldPkg) {
+        return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true);
+    }
+
+    private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg,
+            int[] allUsers, PackageInstalledInfo res) {
+        updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, res);
+    }
+
+    private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg,
+            int[] allUsers, PackageInstalledInfo res) {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
+
+        final String pkgName = pkg.getPackageName();
+        final int[] installedForUsers = res.mOrigUsers;
+        final InstallArgs installArgs = reconciledPkg.mInstallArgs;
+        final int installReason = installArgs.mInstallReason;
+        InstallSource installSource = installArgs.mInstallSource;
+        final String installerPackageName = installSource.installerPackageName;
+
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
+        synchronized (mPm.mLock) {
+            // For system-bundled packages, we assume that installing an upgraded version
+            // of the package implies that the user actually wants to run that new code,
+            // so we enable the package.
+            final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
+            final int userId = installArgs.mUser.getIdentifier();
+            if (ps != null) {
+                if (pkg.isSystem()) {
+                    if (DEBUG_INSTALL) {
+                        Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
+                    }
+                    // Enable system package for requested users
+                    if (res.mOrigUsers != null) {
+                        for (int origUserId : res.mOrigUsers) {
+                            if (userId == UserHandle.USER_ALL || userId == origUserId) {
+                                ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
+                                        origUserId, installerPackageName);
+                            }
+                        }
+                    }
+                    // Also convey the prior install/uninstall state
+                    if (allUsers != null && installedForUsers != null) {
+                        for (int currentUserId : allUsers) {
+                            final boolean installed = ArrayUtils.contains(
+                                    installedForUsers, currentUserId);
+                            if (DEBUG_INSTALL) {
+                                Slog.d(TAG, "    user " + currentUserId + " => " + installed);
+                            }
+                            ps.setInstalled(installed, currentUserId);
+                        }
+                        // these install state changes will be persisted in the
+                        // upcoming call to mSettings.writeLPr().
+                    }
+
+                    if (allUsers != null) {
+                        for (int currentUserId : allUsers) {
+                            ps.resetOverrideComponentLabelIcon(currentUserId);
+                        }
+                    }
+                }
+
+                // Retrieve the overlays for shared libraries of the package.
+                if (!ps.getPkgState().getUsesLibraryInfos().isEmpty()) {
+                    for (SharedLibraryInfo sharedLib : ps.getPkgState().getUsesLibraryInfos()) {
+                        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+                            if (!sharedLib.isDynamic()) {
+                                // TODO(146804378): Support overlaying static shared libraries
+                                continue;
+                            }
+                            final PackageSetting libPs = mPm.mSettings.getPackageLPr(
+                                    sharedLib.getPackageName());
+                            if (libPs == null) {
+                                continue;
+                            }
+                            ps.setOverlayPathsForLibrary(sharedLib.getName(),
+                                    libPs.getOverlayPaths(currentUserId), currentUserId);
+                        }
+                    }
+                }
+
+                // It's implied that when a user requests installation, they want the app to be
+                // installed and enabled. (This does not apply to USER_ALL, which here means only
+                // install on users for which the app is already installed).
+                if (userId != UserHandle.USER_ALL) {
+                    ps.setInstalled(true, userId);
+                    ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
+                }
+
+                mPm.mSettings.addInstallerPackageNames(ps.getInstallSource());
+
+                // When replacing an existing package, preserve the original install reason for all
+                // users that had the package installed before. Similarly for uninstall reasons.
+                final Set<Integer> previousUserIds = new ArraySet<>();
+                if (res.mRemovedInfo != null && res.mRemovedInfo.mInstallReasons != null) {
+                    final int installReasonCount = res.mRemovedInfo.mInstallReasons.size();
+                    for (int i = 0; i < installReasonCount; i++) {
+                        final int previousUserId = res.mRemovedInfo.mInstallReasons.keyAt(i);
+                        final int previousInstallReason =
+                                res.mRemovedInfo.mInstallReasons.valueAt(i);
+                        ps.setInstallReason(previousInstallReason, previousUserId);
+                        previousUserIds.add(previousUserId);
+                    }
+                }
+                if (res.mRemovedInfo != null && res.mRemovedInfo.mUninstallReasons != null) {
+                    for (int i = 0; i < res.mRemovedInfo.mUninstallReasons.size(); i++) {
+                        final int previousUserId = res.mRemovedInfo.mUninstallReasons.keyAt(i);
+                        final int previousReason = res.mRemovedInfo.mUninstallReasons.valueAt(i);
+                        ps.setUninstallReason(previousReason, previousUserId);
+                    }
+                }
+
+                // Set install reason for users that are having the package newly installed.
+                final int[] allUsersList = mPm.mUserManager.getUserIds();
+                if (userId == UserHandle.USER_ALL) {
+                    // TODO(b/152629990): It appears that the package doesn't actually get newly
+                    //  installed in this case, so the installReason shouldn't get modified?
+                    for (int currentUserId : allUsersList) {
+                        if (!previousUserIds.contains(currentUserId)) {
+                            ps.setInstallReason(installReason, currentUserId);
+                        }
+                    }
+                } else if (!previousUserIds.contains(userId)) {
+                    ps.setInstallReason(installReason, userId);
+                }
+
+                // TODO(b/169721400): generalize Incremental States and create a Callback object
+                // that can be used for all the packages.
+                final String codePath = ps.getPathString();
+                if (IncrementalManager.isIncrementalPath(codePath)
+                        && mPm.mIncrementalManager != null) {
+                    mPm.mIncrementalManager.registerLoadingProgressCallback(codePath,
+                            new IncrementalProgressListener(ps.getPackageName(), mPm));
+                }
+
+                // Ensure that the uninstall reason is UNKNOWN for users with the package installed.
+                for (int currentUserId : allUsersList) {
+                    if (ps.getInstalled(currentUserId)) {
+                        ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, currentUserId);
+                    }
+                }
+
+                mPm.mSettings.writeKernelMappingLPr(ps);
+
+                final PermissionManagerServiceInternal.PackageInstalledParams.Builder
+                        permissionParamsBuilder =
+                        new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
+                final boolean grantPermissions = (installArgs.mInstallFlags
+                        & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0;
+                if (grantPermissions) {
+                    final List<String> grantedPermissions =
+                            installArgs.mInstallGrantPermissions != null
+                                    ? Arrays.asList(installArgs.mInstallGrantPermissions)
+                                    : pkg.getRequestedPermissions();
+                    permissionParamsBuilder.setGrantedPermissions(grantedPermissions);
+                }
+                final boolean allowlistAllRestrictedPermissions =
+                        (installArgs.mInstallFlags
+                                & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0;
+                final List<String> allowlistedRestrictedPermissions =
+                        allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions()
+                                : installArgs.mAllowlistedRestrictedPermissions;
+                if (allowlistedRestrictedPermissions != null) {
+                    permissionParamsBuilder.setAllowlistedRestrictedPermissions(
+                            allowlistedRestrictedPermissions);
+                }
+                final int autoRevokePermissionsMode = installArgs.mAutoRevokePermissionsMode;
+                permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
+                final ScanResult scanResult = reconciledPkg.mScanResult;
+                mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId,
+                        permissionParamsBuilder.build(), userId);
+            }
+            res.mName = pkgName;
+            res.mUid = pkg.getUid();
+            res.mPkg = pkg;
+            res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+            //to update install status
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
+            mPm.writeSettingsLPrTEMP();
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+    }
+
+    /**
+     * On successful install, executes remaining steps after commit completes and the package lock
+     * is released. These are typically more expensive or require calls to installd, which often
+     * locks on {@link com.android.server.pm.PackageManagerService.mLock}.
+     */
+    private void executePostCommitSteps(CommitRequest commitRequest) {
+        final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
+        final AppDataHelper appDataHelper = new AppDataHelper(mPm);
+        for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
+            final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags
+                    & SCAN_AS_INSTANT_APP) != 0);
+            final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
+            final String packageName = pkg.getPackageName();
+            final String codePath = pkg.getPath();
+            final boolean onIncremental = mPm.mIncrementalManager != null
+                    && isIncrementalPath(codePath);
+            if (onIncremental) {
+                IncrementalStorage storage = mPm.mIncrementalManager.openStorage(codePath);
+                if (storage == null) {
+                    throw new IllegalArgumentException(
+                            "Install: null storage for incremental package " + packageName);
+                }
+                incrementalStorages.add(storage);
+            }
+            int previousAppId = 0;
+            if (reconciledPkg.mScanResult.needsNewAppId()) {
+                // Only set previousAppId if the app is migrating out of shared UID
+                previousAppId = reconciledPkg.mScanResult.mPreviousAppId;
+            }
+            appDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId);
+            if (reconciledPkg.mPrepareResult.mClearCodeCache) {
+                appDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
+                        FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+                                | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+            }
+            if (reconciledPkg.mPrepareResult.mReplace) {
+                mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
+                        pkg.getBaseApkPath(), pkg.getSplitCodePaths());
+            }
+
+            // Prepare the application profiles for the new code paths.
+            // This needs to be done before invoking dexopt so that any install-time profile
+            // can be used for optimizations.
+            mPm.mArtManagerService.prepareAppProfiles(
+                    pkg,
+                    mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()),
+                    /* updateReferenceProfileContent= */ true);
+
+            // Compute the compilation reason from the installation scenario.
+            final int compilationReason =
+                    mPm.getDexManager().getCompilationReasonForInstallScenario(
+                            reconciledPkg.mInstallArgs.mInstallScenario);
+
+            // Construct the DexoptOptions early to see if we should skip running dexopt.
+            //
+            // Do not run PackageDexOptimizer through the local performDexOpt
+            // method because `pkg` may not be in `mPackages` yet.
+            //
+            // Also, don't fail application installs if the dexopt step fails.
+            final boolean isBackupOrRestore =
+                    reconciledPkg.mInstallArgs.mInstallReason == INSTALL_REASON_DEVICE_RESTORE
+                            || reconciledPkg.mInstallArgs.mInstallReason
+                            == INSTALL_REASON_DEVICE_SETUP;
+
+            final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+                    | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
+                    | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
+            DexoptOptions dexoptOptions =
+                    new DexoptOptions(packageName, compilationReason, dexoptFlags);
+
+            // Check whether we need to dexopt the app.
+            //
+            // NOTE: it is IMPORTANT to call dexopt:
+            //   - after doRename which will sync the package data from AndroidPackage and
+            //     its corresponding ApplicationInfo.
+            //   - after installNewPackageLIF or replacePackageLIF which will update result with the
+            //     uid of the application (pkg.applicationInfo.uid).
+            //     This update happens in place!
+            //
+            // We only need to dexopt if the package meets ALL of the following conditions:
+            //   1) it is not an instant app or if it is then dexopt is enabled via gservices.
+            //   2) it is not debuggable.
+            //   3) it is not on Incremental File System.
+            //
+            // Note that we do not dexopt instant apps by default. dexopt can take some time to
+            // complete, so we skip this step during installation. Instead, we'll take extra time
+            // the first time the instant app starts. It's preferred to do it this way to provide
+            // continuous progress to the useur instead of mysteriously blocking somewhere in the
+            // middle of running an instant app. The default behaviour can be overridden
+            // via gservices.
+            //
+            // Furthermore, dexopt may be skipped, depending on the install scenario and current
+            // state of the device.
+            //
+            // TODO(b/174695087): instantApp and onIncremental should be removed and their install
+            //       path moved to SCENARIO_FAST.
+            final boolean performDexopt =
+                    (!instantApp || android.provider.Settings.Global.getInt(
+                            mPm.mContext.getContentResolver(),
+                            android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
+                            && !pkg.isDebuggable()
+                            && (!onIncremental)
+                            && dexoptOptions.isCompilationEnabled();
+
+            if (performDexopt) {
+                // Compile the layout resources.
+                if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
+                    mPm.mViewCompiler.compileLayouts(pkg);
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                }
+
+                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+                ScanResult result = reconciledPkg.mScanResult;
+
+                // This mirrors logic from commitReconciledScanResultLocked, where the library files
+                // needed for dexopt are assigned.
+                // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous
+                //  setting needs to be passed to have a comparison, hide it behind an immutable
+                //  interface. There's no good reason to have 3 different ways to access the real
+                //  PackageSetting object, only one of which is actually correct.
+                PackageSetting realPkgSetting = result.mExistingSettingCopied
+                        ? result.mRequest.mPkgSetting : result.mPkgSetting;
+                if (realPkgSetting == null) {
+                    realPkgSetting = reconciledPkg.mPkgSetting;
+                }
+
+                // Unfortunately, the updated system app flag is only tracked on this PackageSetting
+                boolean isUpdatedSystemApp = reconciledPkg.mPkgSetting.getPkgState()
+                        .isUpdatedSystemApp();
+
+                realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
+
+                mPm.mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
+                        null /* instructionSets */,
+                        mPm.getOrCreateCompilerPackageStats(pkg),
+                        mPm.getDexManager().getPackageUseInfoOrDefault(packageName),
+                        dexoptOptions);
+                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            }
+
+            // Notify BackgroundDexOptService that the package has been changed.
+            // If this is an update of a package which used to fail to compile,
+            // BackgroundDexOptService will remove it from its denylist.
+            // TODO: Layering violation
+            BackgroundDexOptService.getService().notifyPackageChanged(packageName);
+
+            notifyPackageChangeObserversOnUpdate(reconciledPkg);
+        }
+        waitForNativeBinariesExtraction(incrementalStorages);
+    }
+
+    private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) {
+        final PackageSetting pkgSetting = reconciledPkg.mPkgSetting;
+        final PackageInstalledInfo pkgInstalledInfo = reconciledPkg.mInstallResult;
+        final PackageRemovedInfo pkgRemovedInfo = pkgInstalledInfo.mRemovedInfo;
+
+        PackageChangeEvent pkgChangeEvent = new PackageChangeEvent();
+        pkgChangeEvent.packageName = pkgSetting.getPkg().getPackageName();
+        pkgChangeEvent.version = pkgSetting.getVersionCode();
+        pkgChangeEvent.lastUpdateTimeMillis = pkgSetting.getLastUpdateTime();
+        pkgChangeEvent.newInstalled = (pkgRemovedInfo == null || !pkgRemovedInfo.mIsUpdate);
+        pkgChangeEvent.dataRemoved = (pkgRemovedInfo != null && pkgRemovedInfo.mDataRemoved);
+        pkgChangeEvent.isDeleted = false;
+
+        mPm.notifyPackageChangeObservers(pkgChangeEvent);
+    }
+
+    private static void waitForNativeBinariesExtraction(
+            ArraySet<IncrementalStorage> incrementalStorages) {
+        if (incrementalStorages.isEmpty()) {
+            return;
+        }
+        try {
+            // Native library extraction may take very long time: each page could potentially
+            // wait for either 10s or 100ms (adb vs non-adb data loader), and that easily adds
+            // up to a full watchdog timeout of 1 min, killing the system after that. It doesn't
+            // make much sense as blocking here doesn't lock up the framework, but only blocks
+            // the installation session and the following ones.
+            Watchdog.getInstance().pauseWatchingCurrentThread("native_lib_extract");
+            for (int i = 0; i < incrementalStorages.size(); ++i) {
+                IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i);
+                storage.waitForNativeBinariesExtraction();
+            }
+        } finally {
+            Watchdog.getInstance().resumeWatchingCurrentThread("native_lib_extract");
+        }
+    }
+
+    public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) {
+        String packageName = pkgLite.packageName;
+        int installLocation = pkgLite.installLocation;
+        // reader
+        synchronized (mPm.mLock) {
+            // Currently installed package which the new package is attempting to replace or
+            // null if no such package is installed.
+            AndroidPackage installedPkg = mPm.mPackages.get(packageName);
+
+            if (installedPkg != null) {
+                if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
+                    // Check for updated system application.
+                    if (installedPkg.isSystem()) {
+                        return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                    } else {
+                        // If current upgrade specifies particular preference
+                        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+                            // Application explicitly specified internal.
+                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        } else if (
+                                installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+                            // App explicitly prefers external. Let policy decide
+                        } else {
+                            // Prefer previous location
+                            if (installedPkg.isExternalStorage()) {
+                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                            }
+                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        }
+                    }
+                } else {
+                    // Invalid install. Return error code
+                    return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
+                }
+            }
+        }
+        return pkgLite.recommendedInstallLocation;
+    }
+
+    Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
+            long requiredInstalledVersionCode, int installFlags) {
+        if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
+            return verifyReplacingVersionCodeForApex(
+                    pkgLite, requiredInstalledVersionCode, installFlags);
+        }
+
+        String packageName = pkgLite.packageName;
+        synchronized (mPm.mLock) {
+            // Package which currently owns the data that the new package will own if installed.
+            // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
+            // will be null whereas dataOwnerPkg will contain information about the package
+            // which was uninstalled while keeping its data.
+            AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName);
+            if (dataOwnerPkg  == null) {
+                PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+                if (ps != null) {
+                    dataOwnerPkg = ps.getPkg();
+                }
+            }
+
+            if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
+                if (dataOwnerPkg == null) {
+                    String errorMsg = "Required installed version code was "
+                            + requiredInstalledVersionCode
+                            + " but package is not installed";
+                    Slog.w(TAG, errorMsg);
+                    return Pair.create(
+                            PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
+                }
+
+                if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
+                    String errorMsg = "Required installed version code was "
+                            + requiredInstalledVersionCode
+                            + " but actual installed version is "
+                            + dataOwnerPkg.getLongVersionCode();
+                    Slog.w(TAG, errorMsg);
+                    return Pair.create(
+                            PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
+                }
+            }
+
+            if (dataOwnerPkg != null) {
+                if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
+                        dataOwnerPkg.isDebuggable())) {
+                    try {
+                        checkDowngrade(dataOwnerPkg, pkgLite);
+                    } catch (PackageManagerException e) {
+                        String errorMsg = "Downgrade detected: " + e.getMessage();
+                        Slog.w(TAG, errorMsg);
+                        return Pair.create(
+                                PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+                    }
+                }
+            }
+        }
+        return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
+    }
+
+    private Pair<Integer, String> verifyReplacingVersionCodeForApex(PackageInfoLite pkgLite,
+            long requiredInstalledVersionCode, int installFlags) {
+        String packageName = pkgLite.packageName;
+
+        final PackageInfo activePackage = mPm.mApexManager.getPackageInfo(packageName,
+                ApexManager.MATCH_ACTIVE_PACKAGE);
+        if (activePackage == null) {
+            String errorMsg = "Attempting to install new APEX package " + packageName;
+            Slog.w(TAG, errorMsg);
+            return Pair.create(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED, errorMsg);
+        }
+
+        final long activeVersion = activePackage.getLongVersionCode();
+        if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST
+                && activeVersion != requiredInstalledVersionCode) {
+            String errorMsg = "Installed version of APEX package " + packageName
+                    + " does not match required. Active version: " + activeVersion
+                    + " required: " + requiredInstalledVersionCode;
+            Slog.w(TAG, errorMsg);
+            return Pair.create(PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
+        }
+
+        final boolean isAppDebuggable = (activePackage.applicationInfo.flags
+                & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+        final long newVersionCode = pkgLite.getLongVersionCode();
+        if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, isAppDebuggable)
+                && newVersionCode < activeVersion) {
+            String errorMsg = "Downgrade of APEX package " + packageName
+                    + " is not allowed. Active version: " + activeVersion
+                    + " attempted: " + newVersionCode;
+            Slog.w(TAG, errorMsg);
+            return Pair.create(PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+        }
+
+        return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
+    }
+
+    /**
+     * Check and throw if the given before/after packages would be considered a
+     * downgrade.
+     */
+    private static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
+            throws PackageManagerException {
+        if (after.getLongVersionCode() < before.getLongVersionCode()) {
+            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                    "Update version code " + after.versionCode + " is older than current "
+                            + before.getLongVersionCode());
+        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
+            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
+                throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                        "Update base revision code " + after.baseRevisionCode
+                                + " is older than current " + before.getBaseRevisionCode());
+            }
+
+            if (!ArrayUtils.isEmpty(after.splitNames)) {
+                for (int i = 0; i < after.splitNames.length; i++) {
+                    final String splitName = after.splitNames[i];
+                    final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
+                    if (j != -1) {
+                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
+                            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                                    "Update split " + splitName + " revision code "
+                                            + after.splitRevisionCodes[i]
+                                            + " is older than current "
+                                            + before.getSplitRevisionCodes()[j]);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    int getUidForVerifier(VerifierInfo verifierInfo) {
+        synchronized (mPm.mLock) {
+            final AndroidPackage pkg = mPm.mPackages.get(verifierInfo.packageName);
+            if (pkg == null) {
+                return -1;
+            } else if (pkg.getSigningDetails().getSignatures().length != 1) {
+                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+                        + " has more than one signature; ignoring");
+                return -1;
+            }
+
+            /*
+             * If the public key of the package's signature does not match
+             * our expected public key, then this is a different package and
+             * we should skip.
+             */
+
+            final byte[] expectedPublicKey;
+            try {
+                final Signature verifierSig = pkg.getSigningDetails().getSignatures()[0];
+                final PublicKey publicKey = verifierSig.getPublicKey();
+                expectedPublicKey = publicKey.getEncoded();
+            } catch (CertificateException e) {
+                return -1;
+            }
+
+            final byte[] actualPublicKey = verifierInfo.publicKey.getEncoded();
+
+            if (!Arrays.equals(actualPublicKey, expectedPublicKey)) {
+                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+                        + " does not have the expected public key; ignoring");
+                return -1;
+            }
+
+            return pkg.getUid();
+        }
+    }
+
+    /**
+     * Check whether or not package verification has been enabled.
+     *
+     * @return true if verification should be performed
+     */
+    boolean isVerificationEnabled(PackageInfoLite pkgInfoLite, int userId, int installFlags,
+            int installerUid) {
+        if (!DEFAULT_VERIFY_ENABLE) {
+            return false;
+        }
+
+        // Check if installing from ADB
+        if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
+            if (mPm.isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) {
+                return true;
+            }
+            // Check if the developer wants to skip verification for ADB installs
+            if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) {
+                synchronized (mPm.mLock) {
+                    if (mPm.mSettings.getPackageLPr(pkgInfoLite.packageName) == null) {
+                        // Always verify fresh install
+                        return true;
+                    }
+                }
+                // Only skip when apk is debuggable
+                return !pkgInfoLite.debuggable;
+            }
+            return android.provider.Settings.Global.getInt(mPm.mContext.getContentResolver(),
+                    android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
+        }
+
+        // only when not installed from ADB, skip verification for instant apps when
+        // the installer and verifier are the same.
+        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
+            if (mPm.mInstantAppInstallerActivity != null
+                    && mPm.mInstantAppInstallerActivity.packageName.equals(
+                    mPm.mRequiredVerifierPackage)) {
+                try {
+                    mPm.mInjector.getSystemService(AppOpsManager.class)
+                            .checkPackage(installerUid, mPm.mRequiredVerifierPackage);
+                    if (DEBUG_VERIFY) {
+                        Slog.i(TAG, "disable verification for instant app");
+                    }
+                    return false;
+                } catch (SecurityException ignore) { }
+            }
+        }
+        return true;
+    }
+
+    public void sendPendingBroadcasts() {
+        String[] packages;
+        ArrayList<String>[] components;
+        int size = 0;
+        int[] uids;
+        Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+
+        synchronized (mPm.mLock) {
+            size = mPm.mPendingBroadcasts.size();
+            if (size <= 0) {
+                // Nothing to be done. Just return
+                return;
+            }
+            packages = new String[size];
+            components = new ArrayList[size];
+            uids = new int[size];
+            int i = 0;  // filling out the above arrays
+
+            for (int n = 0; n < mPm.mPendingBroadcasts.userIdCount(); n++) {
+                final int packageUserId = mPm.mPendingBroadcasts.userIdAt(n);
+                final ArrayMap<String, ArrayList<String>> componentsToBroadcast =
+                        mPm.mPendingBroadcasts.packagesForUserId(packageUserId);
+                final int numComponents = componentsToBroadcast.size();
+                for (int index = 0; i < size && index < numComponents; index++) {
+                    packages[i] = componentsToBroadcast.keyAt(index);
+                    components[i] = componentsToBroadcast.valueAt(index);
+                    final PackageSetting ps = mPm.mSettings.getPackageLPr(packages[i]);
+                    uids[i] = (ps != null)
+                            ? UserHandle.getUid(packageUserId, ps.getAppId())
+                            : -1;
+                    i++;
+                }
+            }
+            size = i;
+            mPm.mPendingBroadcasts.clear();
+        }
+        // Send broadcasts
+        for (int i = 0; i < size; i++) {
+            mPm.sendPackageChangedBroadcast(packages[i], true /* dontKillApp */,
+                    components[i], uids[i], null /* reason */);
+        }
+        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+    }
+
+    void handlePackagePostInstall(PackageInstalledInfo res, InstallArgs installArgs,
+            boolean launchedForRestore) {
+        final boolean killApp =
+                (installArgs.mInstallFlags & PackageManager.INSTALL_DONT_KILL_APP) == 0;
+        final boolean virtualPreload =
+                ((installArgs.mInstallFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+        final String installerPackage = installArgs.mInstallSource.installerPackageName;
+        final IPackageInstallObserver2 installObserver = installArgs.mObserver;
+        final int dataLoaderType = installArgs.mDataLoaderType;
+        final boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED;
+        final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null;
+        final String packageName = res.mName;
+        final PackageStateInternal pkgSetting =
+                succeeded ? mPm.getPackageStateInternal(packageName) : null;
+        final boolean removedBeforeUpdate = (pkgSetting == null)
+                || (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals(
+                res.mPkg.getPath()));
+        if (succeeded && removedBeforeUpdate) {
+            Slog.e(TAG, packageName + " was removed before handlePackagePostInstall "
+                    + "could be executed");
+            res.mReturnCode = INSTALL_FAILED_PACKAGE_CHANGED;
+            res.mReturnMsg = "Package was removed before install could complete.";
+
+            // Remove the update failed package's older resources safely now
+            InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null;
+            if (args != null) {
+                synchronized (mPm.mInstallLock) {
+                    args.doPostDeleteLI(true);
+                }
+            }
+            mPm.notifyInstallObserver(res, installObserver);
+            return;
+        }
+
+        if (succeeded) {
+            // Clear the uid cache after we installed a new package.
+            mPm.mPerUidReadTimeoutsCache = null;
+
+            // Send the removed broadcasts
+            if (res.mRemovedInfo != null) {
+                res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
+            }
+
+            final String installerPackageName =
+                    res.mInstallerPackageName != null
+                            ? res.mInstallerPackageName
+                            : res.mRemovedInfo != null
+                                    ? res.mRemovedInfo.mInstallerPackageName
+                                    : null;
+
+            synchronized (mPm.mLock) {
+                mPm.mInstantAppRegistry.onPackageInstalledLPw(res.mPkg, res.mNewUsers);
+            }
+
+            // Determine the set of users who are adding this package for
+            // the first time vs. those who are seeing an update.
+            int[] firstUserIds = EMPTY_INT_ARRAY;
+            int[] firstInstantUserIds = EMPTY_INT_ARRAY;
+            int[] updateUserIds = EMPTY_INT_ARRAY;
+            int[] instantUserIds = EMPTY_INT_ARRAY;
+            final boolean allNewUsers = res.mOrigUsers == null || res.mOrigUsers.length == 0;
+            for (int newUser : res.mNewUsers) {
+                final boolean isInstantApp = pkgSetting.getUserStateOrDefault(newUser)
+                        .isInstantApp();
+                if (allNewUsers) {
+                    if (isInstantApp) {
+                        firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);
+                    } else {
+                        firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);
+                    }
+                    continue;
+                }
+                boolean isNew = true;
+                for (int origUser : res.mOrigUsers) {
+                    if (origUser == newUser) {
+                        isNew = false;
+                        break;
+                    }
+                }
+                if (isNew) {
+                    if (isInstantApp) {
+                        firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);
+                    } else {
+                        firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);
+                    }
+                } else {
+                    if (isInstantApp) {
+                        instantUserIds = ArrayUtils.appendInt(instantUserIds, newUser);
+                    } else {
+                        updateUserIds = ArrayUtils.appendInt(updateUserIds, newUser);
+                    }
+                }
+            }
+
+            // Send installed broadcasts if the package is not a static shared lib.
+            if (res.mPkg.getStaticSharedLibName() == null) {
+                mPm.mProcessLoggingHandler.invalidateBaseApkHash(res.mPkg.getBaseApkPath());
+
+                // Send added for users that see the package for the first time
+                // sendPackageAddedForNewUsers also deals with system apps
+                int appId = UserHandle.getAppId(res.mUid);
+                boolean isSystem = res.mPkg.isSystem();
+                mPm.sendPackageAddedForNewUsers(packageName, isSystem || virtualPreload,
+                        virtualPreload /*startReceiver*/, appId, firstUserIds, firstInstantUserIds,
+                        dataLoaderType);
+
+                // Send added for users that don't see the package for the first time
+                Bundle extras = new Bundle(1);
+                extras.putInt(Intent.EXTRA_UID, res.mUid);
+                if (update) {
+                    extras.putBoolean(Intent.EXTRA_REPLACING, true);
+                }
+                extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+                // Send to all running apps.
+                final SparseArray<int[]> newBroadcastAllowList;
+                synchronized (mPm.mLock) {
+                    newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
+                            mPm.getPackageStateInternal(packageName, Process.SYSTEM_UID),
+                            updateUserIds, mPm.mSettings.getPackagesLocked());
+                }
+                mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+                        extras, 0 /*flags*/,
+                        null /*targetPackage*/, null /*finishedReceiver*/,
+                        updateUserIds, instantUserIds, newBroadcastAllowList, null);
+                if (installerPackageName != null) {
+                    // Send to the installer, even if it's not running.
+                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+                            extras, 0 /*flags*/,
+                            installerPackageName, null /*finishedReceiver*/,
+                            updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
+                }
+                // if the required verifier is defined, but, is not the installer of record
+                // for the package, it gets notified
+                final boolean notifyVerifier = mPm.mRequiredVerifierPackage != null
+                        && !mPm.mRequiredVerifierPackage.equals(installerPackageName);
+                if (notifyVerifier) {
+                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+                            extras, 0 /*flags*/,
+                            mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
+                            updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
+                }
+                // If package installer is defined, notify package installer about new
+                // app installed
+                if (mPm.mRequiredInstallerPackage != null) {
+                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+                            extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
+                            mPm.mRequiredInstallerPackage, null /*finishedReceiver*/,
+                            firstUserIds, instantUserIds, null /* broadcastAllowList */, null);
+                }
+
+                // Send replaced for users that don't see the package for the first time
+                if (update) {
+                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+                            packageName, extras, 0 /*flags*/,
+                            null /*targetPackage*/, null /*finishedReceiver*/,
+                            updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList,
+                            null);
+                    if (installerPackageName != null) {
+                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                                extras, 0 /*flags*/,
+                                installerPackageName, null /*finishedReceiver*/,
+                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
+                    }
+                    if (notifyVerifier) {
+                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                                extras, 0 /*flags*/,
+                                mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
+                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
+                    }
+                    mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
+                            null /*package*/, null /*extras*/, 0 /*flags*/,
+                            packageName /*targetPackage*/,
+                            null /*finishedReceiver*/, updateUserIds, instantUserIds,
+                            null /*broadcastAllowList*/,
+                            mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions(
+                                    REASON_PACKAGE_REPLACED).toBundle());
+                } else if (launchedForRestore && !res.mPkg.isSystem()) {
+                    // First-install and we did a restore, so we're responsible for the
+                    // first-launch broadcast.
+                    if (DEBUG_BACKUP) {
+                        Slog.i(TAG, "Post-restore of " + packageName
+                                + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));
+                    }
+                    mBroadcastHelper.sendFirstLaunchBroadcast(packageName, installerPackage,
+                            firstUserIds, firstInstantUserIds);
+                }
+
+                // Send broadcast package appeared if external for all users
+                if (res.mPkg.isExternalStorage()) {
+                    if (!update) {
+                        final StorageManager storage = mPm.mInjector.getSystemService(
+                                StorageManager.class);
+                        VolumeInfo volume =
+                                storage.findVolumeByUuid(
+                                        StorageManager.convert(
+                                                res.mPkg.getVolumeUuid()).toString());
+                        int packageExternalStorageType =
+                                PackageManagerServiceUtils.getPackageExternalStorageType(volume,
+                                        res.mPkg.isExternalStorage());
+                        // If the package was installed externally, log it.
+                        if (packageExternalStorageType != StorageEnums.UNKNOWN) {
+                            FrameworkStatsLog.write(
+                                    FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED,
+                                    packageExternalStorageType, packageName);
+                        }
+                    }
+                    if (DEBUG_INSTALL) {
+                        Slog.i(TAG, "upgrading pkg " + res.mPkg + " is external");
+                    }
+                    final int[] uidArray = new int[]{res.mPkg.getUid()};
+                    ArrayList<String> pkgList = new ArrayList<>(1);
+                    pkgList.add(packageName);
+                    mBroadcastHelper.sendResourcesChangedBroadcast(
+                            true, true, pkgList, uidArray, null);
+                }
+            } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
+                for (int i = 0; i < res.mLibraryConsumers.size(); i++) {
+                    AndroidPackage pkg = res.mLibraryConsumers.get(i);
+                    // send broadcast that all consumers of the static shared library have changed
+                    mPm.sendPackageChangedBroadcast(pkg.getPackageName(), false /* dontKillApp */,
+                            new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
+                            pkg.getUid(), null);
+                }
+            }
+
+            // Work that needs to happen on first install within each user
+            if (firstUserIds.length > 0) {
+                for (int userId : firstUserIds) {
+                    mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
+                            userId);
+                }
+            }
+
+            if (allNewUsers && !update) {
+                mPm.notifyPackageAdded(packageName, res.mUid);
+            } else {
+                mPm.notifyPackageChanged(packageName, res.mUid);
+            }
+
+            // Log current value of "unknown sources" setting
+            EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
+                    getUnknownSourcesSettings());
+
+            // Remove the replaced package's older resources safely now
+            InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null;
+            if (args != null) {
+                if (!killApp) {
+                    // If we didn't kill the app, defer the deletion of code/resource files, since
+                    // they may still be in use by the running application. This mitigates problems
+                    // in cases where resources or code is loaded by a new Activity before
+                    // ApplicationInfo changes have propagated to all application threads.
+                    mPm.scheduleDeferredNoKillPostDelete(args);
+                } else {
+                    synchronized (mPm.mInstallLock) {
+                        args.doPostDeleteLI(true);
+                    }
+                }
+            } else {
+                // Force a gc to clear up things. Ask for a background one, it's fine to go on
+                // and not block here.
+                VMRuntime.getRuntime().requestConcurrentGC();
+            }
+
+            // Notify DexManager that the package was installed for new users.
+            // The updated users should already be indexed and the package code paths
+            // should not change.
+            // Don't notify the manager for ephemeral apps as they are not expected to
+            // survive long enough to benefit of background optimizations.
+            for (int userId : firstUserIds) {
+                PackageInfo info = mPm.getPackageInfo(packageName, /*flags*/ 0, userId);
+                // There's a race currently where some install events may interleave with an
+                // uninstall. This can lead to package info being null (b/36642664).
+                if (info != null) {
+                    mPm.getDexManager().notifyPackageInstalled(info, userId);
+                }
+            }
+        }
+
+        final boolean deferInstallObserver = succeeded && update && !killApp;
+        if (deferInstallObserver) {
+            mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+        } else {
+            mPm.notifyInstallObserver(res, installObserver);
+        }
+
+        // Prune unused static shared libraries which have been cached a period of time
+        mPm.schedulePruneUnusedStaticSharedLibraries(true /* delay */);
+
+        // Log tracing if needed
+        if (installArgs.mTraceMethod != null) {
+            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, installArgs.mTraceMethod,
+                    installArgs.mTraceCookie);
+        }
+    }
+
+    /**
+     * Get the "allow unknown sources" setting.
+     *
+     * @return the current "allow unknown sources" setting
+     */
+    private int getUnknownSourcesSettings() {
+        return android.provider.Settings.Secure.getIntForUser(mPm.mContext.getContentResolver(),
+                android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS,
+                -1, UserHandle.USER_SYSTEM);
+    }
+
+    /**
+     * Uncompress and install stub applications.
+     * <p>In order to save space on the system partition, some applications are shipped in a
+     * compressed form. In addition the compressed bits for the full application, the
+     * system image contains a tiny stub comprised of only the Android manifest.
+     * <p>During the first boot, attempt to uncompress and install the full application. If
+     * the application can't be installed for any reason, disable the stub and prevent
+     * uncompressing the full application during future boots.
+     * <p>In order to forcefully attempt an installation of a full application, go to app
+     * settings and enable the application.
+     */
+    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
+    void installSystemStubPackages(@NonNull List<String> systemStubPackageNames,
+            @PackageManagerService.ScanFlags int scanFlags) {
+        for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) {
+            final String packageName = systemStubPackageNames.get(i);
+            // skip if the system package is already disabled
+            if (mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
+                systemStubPackageNames.remove(i);
+                continue;
+            }
+            // skip if the package isn't installed (?!); this should never happen
+            final AndroidPackage pkg = mPm.mPackages.get(packageName);
+            if (pkg == null) {
+                systemStubPackageNames.remove(i);
+                continue;
+            }
+            // skip if the package has been disabled by the user
+            final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps != null) {
+                final int enabledState = ps.getEnabled(UserHandle.USER_SYSTEM);
+                if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+                    systemStubPackageNames.remove(i);
+                    continue;
+                }
+            }
+
+            // install the package to replace the stub on /system
+            try {
+                installStubPackageLI(pkg, 0, scanFlags);
+                ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                        UserHandle.USER_SYSTEM, "android");
+                systemStubPackageNames.remove(i);
+            } catch (PackageManagerException e) {
+                Slog.e(TAG, "Failed to parse uncompressed system package: " + e.getMessage());
+            }
+
+            // any failed attempt to install the package will be cleaned up later
+        }
+
+        // disable any stub still left; these failed to install the full application
+        for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) {
+            final String pkgName = systemStubPackageNames.get(i);
+            final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
+            ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    UserHandle.USER_SYSTEM, "android");
+            logCriticalInfo(Log.ERROR, "Stub disabled; pkg: " + pkgName);
+        }
+    }
+
+    /**
+     * Extract, install and enable a stub package.
+     * <p>If the compressed file can not be extracted / installed for any reason, the stub
+     * APK will be installed and the package will be disabled. To recover from this situation,
+     * the user will need to go into system settings and re-enable the package.
+     */
+    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
+    boolean enableCompressedPackage(AndroidPackage stubPkg,
+            @NonNull PackageSetting stubPkgSetting) {
+        final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
+                | ParsingPackageUtils.PARSE_ENFORCE_CODE;
+        synchronized (mPm.mInstallLock) {
+            final AndroidPackage pkg;
+            try (PackageFreezer freezer =
+                         mPm.freezePackage(stubPkg.getPackageName(), "setEnabledSetting")) {
+                pkg = installStubPackageLI(stubPkg, parseFlags, 0 /*scanFlags*/);
+                synchronized (mPm.mLock) {
+                    mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
+                    try {
+                        mPm.updateSharedLibrariesLocked(pkg, stubPkgSetting, null, null,
+                                Collections.unmodifiableMap(mPm.mPackages));
+                    } catch (PackageManagerException e) {
+                        Slog.w(TAG, "updateAllSharedLibrariesLPw failed: ", e);
+                    }
+                    mPm.mPermissionManager.onPackageInstalled(pkg,
+                            Process.INVALID_UID /* previousAppId */,
+                            PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
+                            UserHandle.USER_ALL);
+                    mPm.writeSettingsLPrTEMP();
+                }
+            } catch (PackageManagerException e) {
+                // Whoops! Something went very wrong; roll back to the stub and disable the package
+                try (PackageFreezer freezer =
+                             mPm.freezePackage(stubPkg.getPackageName(), "setEnabledSetting")) {
+                    synchronized (mPm.mLock) {
+                        // NOTE: Ensure the system package is enabled; even for a compressed stub.
+                        // If we don't, installing the system package fails during scan
+                        mPm.mSettings.enableSystemPackageLPw(stubPkg.getPackageName());
+                    }
+                    installPackageFromSystemLIF(stubPkg.getPath(),
+                            mPm.mUserManager.getUserIds() /*allUserHandles*/,
+                            null /*origUserHandles*/,
+                            true /*writeSettings*/);
+                } catch (PackageManagerException pme) {
+                    // Serious WTF; we have to be able to install the stub
+                    Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(),
+                            pme);
+                } finally {
+                    // Disable the package; the stub by itself is not runnable
+                    synchronized (mPm.mLock) {
+                        final PackageSetting stubPs = mPm.mSettings.getPackageLPr(
+                                stubPkg.getPackageName());
+                        if (stubPs != null) {
+                            stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED,
+                                    UserHandle.USER_SYSTEM, "android");
+                        }
+                        mPm.writeSettingsLPrTEMP();
+                    }
+                }
+                return false;
+            }
+            mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
+                    FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+                            | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+            mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
+                    pkg.getBaseApkPath(), pkg.getSplitCodePaths());
+        }
+        return true;
+    }
+
+    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
+    private AndroidPackage installStubPackageLI(AndroidPackage stubPkg,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags)
+            throws PackageManagerException {
+        if (DEBUG_COMPRESSION) {
+            Slog.i(TAG, "Uncompressing system stub; pkg: " + stubPkg.getPackageName());
+        }
+        // uncompress the binary to its eventual destination on /data
+        final File scanFile = decompressPackage(stubPkg.getPackageName(), stubPkg.getPath());
+        if (scanFile == null) {
+            throw new PackageManagerException(
+                    "Unable to decompress stub at " + stubPkg.getPath());
+        }
+        synchronized (mPm.mLock) {
+            mPm.mSettings.disableSystemPackageLPw(stubPkg.getPackageName(), true /*replaced*/);
+        }
+        final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
+        removePackageHelper.removePackageLI(stubPkg, true /*chatty*/);
+        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mPm);
+        try {
+            return scanPackageHelper.scanPackageTracedLI(scanFile, parseFlags, scanFlags, 0, null);
+        } catch (PackageManagerException e) {
+            Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
+                    e);
+            // Remove the failed install
+            removePackageHelper.removeCodePathLI(scanFile);
+            throw e;
+        }
+    }
+
+    /**
+     * Decompresses the given package on the system image onto
+     * the /data partition.
+     * @return The directory the package was decompressed into. Otherwise, {@code null}.
+     */
+    @GuardedBy("mPm.mInstallLock")
+    private File decompressPackage(String packageName, String codePath) {
+        if (!compressedFileExists(codePath)) {
+            if (DEBUG_COMPRESSION) {
+                Slog.i(TAG, "No files to decompress at: " + codePath);
+            }
+            return null;
+        }
+        final File dstCodePath =
+                PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null),
+                        packageName);
+        int ret = PackageManagerServiceUtils.decompressFiles(codePath, dstCodePath, packageName);
+        if (ret == PackageManager.INSTALL_SUCCEEDED) {
+            ret = extractNativeBinaries(dstCodePath, packageName);
+        }
+        if (ret == PackageManager.INSTALL_SUCCEEDED) {
+            // NOTE: During boot, we have to delay releasing cblocks for no other reason than
+            // we cannot retrieve the setting {@link Secure#RELEASE_COMPRESS_BLOCKS_ON_INSTALL}.
+            // When we no longer need to read that setting, cblock release can occur always
+            // occur here directly
+            if (!mPm.isSystemReady()) {
+                if (mPm.mReleaseOnSystemReady == null) {
+                    mPm.mReleaseOnSystemReady = new ArrayList<>();
+                }
+                mPm.mReleaseOnSystemReady.add(dstCodePath);
+            } else {
+                final ContentResolver resolver = mPm.mContext.getContentResolver();
+                F2fsUtils.releaseCompressedBlocks(resolver, dstCodePath);
+            }
+        } else {
+            if (!dstCodePath.exists()) {
+                return null;
+            }
+            new RemovePackageHelper(mPm).removeCodePathLI(dstCodePath);
+            return null;
+        }
+
+        return dstCodePath;
+    }
+
+    private static int extractNativeBinaries(File dstCodePath, String packageName) {
+        final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME);
+        NativeLibraryHelper.Handle handle = null;
+        try {
+            handle = NativeLibraryHelper.Handle.create(dstCodePath);
+            return NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
+                    null /*abiOverride*/, false /*isIncremental*/);
+        } catch (IOException e) {
+            logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
+                    + "; pkg: " + packageName);
+            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+        } finally {
+            IoUtils.closeQuietly(handle);
+        }
+    }
+
+    /**
+     * Tries to restore the disabled system package after an update has been deleted.
+     */
+    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
+    public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
+            PackageSetting deletedPs, @NonNull int[] allUserHandles,
+            @Nullable PackageRemovedInfo outInfo,
+            boolean writeSettings,
+            PackageSetting disabledPs)
+            throws SystemDeleteException {
+        // writer
+        synchronized (mPm.mLock) {
+            // NOTE: The system package always needs to be enabled; even if it's for
+            // a compressed stub. If we don't, installing the system package fails
+            // during scan [scanning checks the disabled packages]. We will reverse
+            // this later, after we've "installed" the stub.
+            // Reinstate the old system package
+            mPm.mSettings.enableSystemPackageLPw(disabledPs.getPkg().getPackageName());
+            // Remove any native libraries from the upgraded package.
+            removeNativeBinariesLI(deletedPs);
+        }
+
+        // Install the system package
+        if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
+        try {
+            installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
+                    outInfo == null ? null : outInfo.mOrigUsers, writeSettings);
+        } catch (PackageManagerException e) {
+            Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
+                    + e.getMessage());
+            // TODO(b/194319951): can we avoid this; throw would come from scan...
+            throw new SystemDeleteException(e);
+        } finally {
+            if (disabledPs.getPkg().isStub()) {
+                // We've re-installed the stub; make sure it's disabled here. If package was
+                // originally enabled, we'll install the compressed version of the application
+                // and re-enable it afterward.
+                disableStubPackage(action, deletedPs, allUserHandles);
+            }
+        }
+    }
+
+    @GuardedBy("mPm.mLock")
+    private void disableStubPackage(DeletePackageAction action, PackageSetting deletedPs,
+            @NonNull int[] allUserHandles) {
+        final PackageSetting stubPs = mPm.mSettings.getPackageLPr(
+                deletedPs.getPackageName());
+        if (stubPs != null) {
+            int userId = action.mUser == null
+                    ? UserHandle.USER_ALL : action.mUser.getIdentifier();
+            if (userId == UserHandle.USER_ALL) {
+                for (int aUserId : allUserHandles) {
+                    stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, aUserId, "android");
+                }
+            } else if (userId >= UserHandle.USER_SYSTEM) {
+                stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, userId, "android");
+            }
+        }
+    }
+
+    private static void removeNativeBinariesLI(PackageSetting ps) {
+        if (ps != null) {
+            NativeLibraryHelper.removeNativeBinariesLI(ps.getLegacyNativeLibraryPath());
+        }
+    }
+
+    /**
+     * Installs a package that's already on the system partition.
+     */
+    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
+    private void installPackageFromSystemLIF(@NonNull String codePathString,
+            @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings)
+            throws PackageManagerException {
+        final File codePath = new File(codePathString);
+        @ParsingPackageUtils.ParseFlags int parseFlags =
+                mPm.getDefParseFlags()
+                        | ParsingPackageUtils.PARSE_MUST_BE_APK
+                        | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
+        @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
+        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mPm);
+        final AndroidPackage pkg =
+                scanPackageHelper.scanPackageTracedLI(
+                        codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
+
+        PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
+
+        try {
+            // update shared libraries for the newly re-installed system package
+            mPm.updateSharedLibrariesLocked(pkg, pkgSetting, null, null,
+                    Collections.unmodifiableMap(mPm.mPackages));
+        } catch (PackageManagerException e) {
+            Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
+        }
+
+        mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
+
+        setPackageInstalledForSystemPackage(pkg, allUserHandles,
+                origUserHandles, writeSettings);
+    }
+
+    private void setPackageInstalledForSystemPackage(@NonNull AndroidPackage pkg,
+            @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings) {
+        // writer
+        synchronized (mPm.mLock) {
+            PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
+
+            final boolean applyUserRestrictions = origUserHandles != null;
+            if (applyUserRestrictions) {
+                boolean installedStateChanged = false;
+                if (DEBUG_REMOVE) {
+                    Slog.d(TAG, "Propagating install state across reinstall");
+                }
+                for (int userId : allUserHandles) {
+                    final boolean installed = ArrayUtils.contains(origUserHandles, userId);
+                    if (DEBUG_REMOVE) {
+                        Slog.d(TAG, "    user " + userId + " => " + installed);
+                    }
+                    if (installed != ps.getInstalled(userId)) {
+                        installedStateChanged = true;
+                    }
+                    ps.setInstalled(installed, userId);
+                    if (installed) {
+                        ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
+                    }
+                }
+                // Regardless of writeSettings we need to ensure that this restriction
+                // state propagation is persisted
+                mPm.mSettings.writeAllUsersPackageRestrictionsLPr();
+                if (installedStateChanged) {
+                    mPm.mSettings.writeKernelMappingLPr(ps);
+                }
+            }
+
+            // The method below will take care of removing obsolete permissions and granting
+            // install permissions.
+            mPm.mPermissionManager.onPackageInstalled(pkg,
+                    Process.INVALID_UID /* previousAppId */,
+                    PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
+                    UserHandle.USER_ALL);
+            for (final int userId : allUserHandles) {
+                if (applyUserRestrictions) {
+                    mPm.mSettings.writePermissionStateForUserLPr(userId, false);
+                }
+            }
+
+            // can downgrade to reader here
+            if (writeSettings) {
+                mPm.writeSettingsLPrTEMP();
+            }
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index 56cb4ad..0dff7d1 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -17,131 +17,42 @@
 package com.android.server.pm;
 
 import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
-import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
-import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_PERMISSION_GROUP;
-import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
-import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
-import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID;
-import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
-import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
-import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
-import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static android.os.incremental.IncrementalManager.isIncrementalPath;
-import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
-import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
-import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTANT;
 import static com.android.server.pm.PackageManagerService.INIT_COPY;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.PackageManagerService.PRECOMPILE_LAYOUTS;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_ODM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
-import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
-import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
-import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
-import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
-import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
-import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
 import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
-import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ApplicationPackageManager;
 import android.content.pm.DataLoaderType;
 import android.content.pm.IPackageInstallObserver2;
-import android.content.pm.PackageChangeEvent;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
-import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
-import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.parsing.PackageLite;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.component.ComponentMutateUtils;
-import android.content.pm.parsing.component.ParsedPermission;
-import android.content.pm.parsing.component.ParsedPermissionGroup;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
-import android.net.Uri;
-import android.os.Build;
 import android.os.Environment;
 import android.os.Message;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.incremental.IncrementalManager;
-import android.os.incremental.IncrementalStorage;
 import android.os.storage.StorageManager;
 import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.EventLog;
 import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.PackageHelper;
-import com.android.internal.security.VerityUtils;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
-import com.android.server.Watchdog;
-import com.android.server.pm.dex.DexoptOptions;
-import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.permission.Permission;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
-import com.android.server.pm.pkg.PackageStateInternal;
-
-import libcore.io.IoUtils;
 
 import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.security.DigestException;
-import java.security.DigestInputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
 
 final class InstallParams extends HandlerParams {
     final OriginInfo mOriginInfo;
@@ -165,7 +76,6 @@
     final int mDataLoaderType;
     final long mRequiredInstalledVersionCode;
     final PackageLite mPackageLite;
-    final InstallPackageHelper mInstallPackageHelper;
 
     InstallParams(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
             int installFlags, InstallSource installSource, String volumeUuid,
@@ -190,7 +100,6 @@
         mDataLoaderType = DataLoaderType.NONE;
         mRequiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
         mPackageLite = packageLite;
-        mInstallPackageHelper = new InstallPackageHelper(mPm);
     }
 
     InstallParams(File stagedDir, IPackageInstallObserver2 observer,
@@ -217,7 +126,6 @@
                 ? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
         mRequiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
         mPackageLite = packageLite;
-        mInstallPackageHelper = new InstallPackageHelper(mPm);
     }
 
     @Override
@@ -226,45 +134,6 @@
                 + " file=" + mOriginInfo.mFile + "}";
     }
 
-    private int installLocationPolicy(PackageInfoLite pkgLite) {
-        String packageName = pkgLite.packageName;
-        int installLocation = pkgLite.installLocation;
-        // reader
-        synchronized (mPm.mLock) {
-            // Currently installed package which the new package is attempting to replace or
-            // null if no such package is installed.
-            AndroidPackage installedPkg = mPm.mPackages.get(packageName);
-
-            if (installedPkg != null) {
-                if ((mInstallFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
-                    // Check for updated system application.
-                    if (installedPkg.isSystem()) {
-                        return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
-                    } else {
-                        // If current upgrade specifies particular preference
-                        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
-                            // Application explicitly specified internal.
-                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
-                        } else if (
-                                installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
-                            // App explicitly prefers external. Let policy decide
-                        } else {
-                            // Prefer previous location
-                            if (installedPkg.isExternalStorage()) {
-                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
-                            }
-                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
-                        }
-                    }
-                } else {
-                    // Invalid install. Return error code
-                    return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
-                }
-            }
-        }
-        return pkgLite.recommendedInstallLocation;
-    }
-
     /**
      * Override install location based on default policy if needed.
      *
@@ -338,7 +207,7 @@
             ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
         } else {
             // Override with defaults if needed.
-            loc = installLocationPolicy(pkgLite);
+            loc = mInstallPackageHelper.installLocationPolicy(pkgLite, mInstallFlags);
 
             final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
 
@@ -375,7 +244,7 @@
         // state can change within this delay and hence we need to re-verify certain conditions.
         boolean isStaged = (mInstallFlags & INSTALL_STAGED) != 0;
         if (isStaged) {
-            Pair<Integer, String> ret = verifyReplacingVersionCode(
+            Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode(
                     pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
             mRet = ret.first;
             if (mRet != INSTALL_SUCCEEDED) {
@@ -422,1609 +291,10 @@
     private void processInstallRequestsAsync(boolean success,
             List<InstallRequest> installRequests) {
         mPm.mHandler.post(() -> {
-            List<InstallRequest> apexInstallRequests = new ArrayList<>();
-            List<InstallRequest> apkInstallRequests = new ArrayList<>();
-            for (InstallRequest request : installRequests) {
-                if ((request.mArgs.mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
-                    apexInstallRequests.add(request);
-                } else {
-                    apkInstallRequests.add(request);
-                }
-            }
-            // Note: supporting multi package install of both APEXes and APKs might requir some
-            // thinking to ensure atomicity of the install.
-            if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) {
-                // This should've been caught at the validation step, but for some reason wasn't.
-                throw new IllegalStateException(
-                        "Attempted to do a multi package install of both APEXes and APKs");
-            }
-            if (!apexInstallRequests.isEmpty()) {
-                if (success) {
-                    // Since installApexPackages requires talking to external service (apexd), we
-                    // schedule to run it async. Once it finishes, it will resume the install.
-                    Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),
-                            "installApexPackages");
-                    t.start();
-                } else {
-                    // Non-staged APEX installation failed somewhere before
-                    // processInstallRequestAsync. In that case just notify the observer about the
-                    // failure.
-                    InstallRequest request = apexInstallRequests.get(0);
-                    mPm.notifyInstallObserver(request.mInstallResult,
-                            request.mArgs.mObserver);
-                }
-                return;
-            }
-            if (success) {
-                for (InstallRequest request : apkInstallRequests) {
-                    request.mArgs.doPreInstall(request.mInstallResult.mReturnCode);
-                }
-                synchronized (mPm.mInstallLock) {
-                    installPackagesTracedLI(apkInstallRequests);
-                }
-                for (InstallRequest request : apkInstallRequests) {
-                    request.mArgs.doPostInstall(
-                            request.mInstallResult.mReturnCode, request.mInstallResult.mUid);
-                }
-            }
-            for (InstallRequest request : apkInstallRequests) {
-                mInstallPackageHelper.restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
-                        request.mInstallResult,
-                        new PostInstallData(request.mArgs,
-                                request.mInstallResult, null));
-            }
+            mInstallPackageHelper.processInstallRequests(success, installRequests);
         });
     }
 
-    private void installApexPackagesTraced(List<InstallRequest> requests) {
-        try {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installApexPackages");
-            installApexPackages(requests);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
-    private void installApexPackages(List<InstallRequest> requests) {
-        if (requests.isEmpty()) {
-            return;
-        }
-        if (requests.size() != 1) {
-            throw new IllegalStateException(
-                    "Only a non-staged install of a single APEX is supported");
-        }
-        InstallRequest request = requests.get(0);
-        try {
-            // Should directory scanning logic be moved to ApexManager for better test coverage?
-            final File dir = request.mArgs.mOriginInfo.mResolvedFile;
-            final File[] apexes = dir.listFiles();
-            if (apexes == null) {
-                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                        dir.getAbsolutePath() + " is not a directory");
-            }
-            if (apexes.length != 1) {
-                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                        "Expected exactly one .apex file under " + dir.getAbsolutePath()
-                                + " got: " + apexes.length);
-            }
-            try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
-                mPm.mApexManager.installPackage(apexes[0], packageParser);
-            }
-        } catch (PackageManagerException e) {
-            request.mInstallResult.setError("APEX installation failed", e);
-        }
-        PackageManagerService.invalidatePackageInfoCache();
-        mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver);
-    }
-
-    @GuardedBy("mInstallLock")
-    private void installPackagesTracedLI(List<InstallRequest> requests) {
-        try {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
-            installPackagesLI(requests);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
-    /**
-     * Installs one or more packages atomically. This operation is broken up into four phases:
-     * <ul>
-     *     <li><b>Prepare</b>
-     *         <br/>Analyzes any current install state, parses the package and does initial
-     *         validation on it.</li>
-     *     <li><b>Scan</b>
-     *         <br/>Interrogates the parsed packages given the context collected in prepare.</li>
-     *     <li><b>Reconcile</b>
-     *         <br/>Validates scanned packages in the context of each other and the current system
-     *         state to ensure that the install will be successful.
-     *     <li><b>Commit</b>
-     *         <br/>Commits all scanned packages and updates system state. This is the only place
-     *         that system state may be modified in the install flow and all predictable errors
-     *         must be determined before this phase.</li>
-     * </ul>
-     *
-     * Failure at any phase will result in a full failure to install all packages.
-     */
-    @GuardedBy("mInstallLock")
-    private void installPackagesLI(List<InstallRequest> requests) {
-        final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
-        final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size());
-        final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size());
-        final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
-        final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
-        final Map<String, PackageSetting> lastStaticSharedLibSettings =
-                new ArrayMap<>(requests.size());
-        final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
-        boolean success = false;
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mPm);
-        try {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
-            for (InstallRequest request : requests) {
-                // TODO(b/109941548): remove this once we've pulled everything from it and into
-                //                    scan, reconcile or commit.
-                final PrepareResult prepareResult;
-                try {
-                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
-                    prepareResult =
-                            preparePackageLI(request.mArgs, request.mInstallResult);
-                } catch (PrepareFailure prepareFailure) {
-                    request.mInstallResult.setError(prepareFailure.error,
-                            prepareFailure.getMessage());
-                    request.mInstallResult.mOrigPackage = prepareFailure.mConflictingPackage;
-                    request.mInstallResult.mOrigPermission = prepareFailure.mConflictingPermission;
-                    return;
-                } finally {
-                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                }
-                request.mInstallResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
-                request.mInstallResult.mInstallerPackageName =
-                        request.mArgs.mInstallSource.installerPackageName;
-
-                final String packageName = prepareResult.mPackageToScan.getPackageName();
-                prepareResults.put(packageName, prepareResult);
-                installResults.put(packageName, request.mInstallResult);
-                installArgs.put(packageName, request.mArgs);
-                try {
-                    final ScanResult result = scanPackageHelper.scanPackageTracedLI(
-                            prepareResult.mPackageToScan, prepareResult.mParseFlags,
-                            prepareResult.mScanFlags, System.currentTimeMillis(),
-                            request.mArgs.mUser, request.mArgs.mAbiOverride);
-                    if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(),
-                            result)) {
-                        request.mInstallResult.setError(
-                                PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
-                                "Duplicate package "
-                                        + result.mPkgSetting.getPkg().getPackageName()
-                                        + " in multi-package install request.");
-                        return;
-                    }
-                    createdAppId.put(packageName,
-                            scanPackageHelper.optimisticallyRegisterAppId(result));
-                    versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
-                            mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
-                    if (result.mStaticSharedLibraryInfo != null) {
-                        final PackageSetting sharedLibLatestVersionSetting =
-                                mPm.getSharedLibLatestVersionSetting(result);
-                        if (sharedLibLatestVersionSetting != null) {
-                            lastStaticSharedLibSettings.put(
-                                    result.mPkgSetting.getPkg().getPackageName(),
-                                    sharedLibLatestVersionSetting);
-                        }
-                    }
-                } catch (PackageManagerException e) {
-                    request.mInstallResult.setError("Scanning Failed.", e);
-                    return;
-                }
-            }
-            ReconcileRequest
-                    reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
-                    installResults,
-                    prepareResults,
-                    mPm.mSharedLibraries,
-                    Collections.unmodifiableMap(mPm.mPackages), versionInfos,
-                    lastStaticSharedLibSettings);
-            CommitRequest commitRequest = null;
-            synchronized (mPm.mLock) {
-                Map<String, ReconciledPackage> reconciledPackages;
-                try {
-                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
-                    reconciledPackages = mInstallPackageHelper.reconcilePackagesLocked(
-                            reconcileRequest, mPm.mSettings.getKeySetManagerService(),
-                            mPm.mInjector);
-                } catch (ReconcileFailure e) {
-                    for (InstallRequest request : requests) {
-                        request.mInstallResult.setError("Reconciliation failed...", e);
-                    }
-                    return;
-                } finally {
-                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                }
-                try {
-                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
-                    commitRequest = new CommitRequest(reconciledPackages,
-                            mPm.mUserManager.getUserIds());
-                    commitPackagesLocked(commitRequest);
-                    success = true;
-                } finally {
-                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                }
-            }
-            executePostCommitSteps(commitRequest);
-        } finally {
-            if (success) {
-                for (InstallRequest request : requests) {
-                    final InstallArgs args = request.mArgs;
-                    if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) {
-                        continue;
-                    }
-                    if (args.mSigningDetails.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) {
-                        continue;
-                    }
-                    // For incremental installs, we bypass the verifier prior to install. Now
-                    // that we know the package is valid, send a notice to the verifier with
-                    // the root hash of the base.apk.
-                    final String baseCodePath = request.mInstallResult.mPkg.getBaseApkPath();
-                    final String[] splitCodePaths = request.mInstallResult.mPkg.getSplitCodePaths();
-                    final Uri originUri = Uri.fromFile(args.mOriginInfo.mResolvedFile);
-                    final int verificationId = mPm.mPendingVerificationToken++;
-                    final String rootHashString = PackageManagerServiceUtils
-                            .buildVerificationRootHashString(baseCodePath, splitCodePaths);
-                    mVerificationHelper.broadcastPackageVerified(verificationId, originUri,
-                            PackageManager.VERIFICATION_ALLOW, rootHashString,
-                            args.mDataLoaderType, args.getUser());
-                }
-            } else {
-                for (ScanResult result : preparedScans.values()) {
-                    if (createdAppId.getOrDefault(result.mRequest.mParsedPackage.getPackageName(),
-                            false)) {
-                        scanPackageHelper.cleanUpAppIdCreation(result);
-                    }
-                }
-                // TODO(b/194319951): create a more descriptive reason than unknown
-                // mark all non-failure installs as UNKNOWN so we do not treat them as success
-                for (InstallRequest request : requests) {
-                    if (request.mInstallResult.mFreezer != null) {
-                        request.mInstallResult.mFreezer.close();
-                    }
-                    if (request.mInstallResult.mReturnCode == PackageManager.INSTALL_SUCCEEDED) {
-                        request.mInstallResult.mReturnCode = PackageManager.INSTALL_UNKNOWN;
-                    }
-                }
-            }
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
-    @GuardedBy("mInstallLock")
-    private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
-            throws PrepareFailure {
-        final int installFlags = args.mInstallFlags;
-        final File tmpPackageFile = new File(args.getCodePath());
-        final boolean onExternal = args.mVolumeUuid != null;
-        final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
-        final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
-        final boolean virtualPreload =
-                ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
-        final boolean isRollback = args.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
-        @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
-        if (args.mMoveInfo != null) {
-            // moving a complete application; perform an initial scan on the new install location
-            scanFlags |= SCAN_INITIAL;
-        }
-        if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
-            scanFlags |= SCAN_DONT_KILL_APP;
-        }
-        if (instantApp) {
-            scanFlags |= SCAN_AS_INSTANT_APP;
-        }
-        if (fullApp) {
-            scanFlags |= SCAN_AS_FULL_APP;
-        }
-        if (virtualPreload) {
-            scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
-        }
-
-        if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
-
-        // Validity check
-        if (instantApp && onExternal) {
-            Slog.i(TAG, "Incompatible ephemeral install; external=" + onExternal);
-            throw new PrepareFailure(PackageManager.INSTALL_FAILED_SESSION_INVALID);
-        }
-
-        // Retrieve PackageSettings and parse package
-        @ParsingPackageUtils.ParseFlags final int parseFlags =
-                mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
-                | ParsingPackageUtils.PARSE_ENFORCE_CODE
-                | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
-
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
-        final ParsedPackage parsedPackage;
-        try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
-            parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
-            AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
-        } catch (PackageManagerException e) {
-            throw new PrepareFailure("Failed parse during installPackageLI", e);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-
-        // Instant apps have several additional install-time checks.
-        if (instantApp) {
-            if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) {
-                Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
-                        + " does not target at least O");
-                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
-                        "Instant app package must target at least O");
-            }
-            if (parsedPackage.getSharedUserId() != null) {
-                Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
-                        + " may not declare sharedUserId.");
-                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
-                        "Instant app package may not declare a sharedUserId");
-            }
-        }
-
-        if (parsedPackage.isStaticSharedLibrary()) {
-            // Static shared libraries have synthetic package names
-            PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
-
-            // No static shared libs on external storage
-            if (onExternal) {
-                Slog.i(TAG, "Static shared libs can only be installed on internal storage.");
-                throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
-                        "Packages declaring static-shared libs cannot be updated");
-            }
-        }
-
-        String pkgName = res.mName = parsedPackage.getPackageName();
-        if (parsedPackage.isTestOnly()) {
-            if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
-                throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
-            }
-        }
-
-        // either use what we've been given or parse directly from the APK
-        if (args.mSigningDetails != SigningDetails.UNKNOWN) {
-            parsedPackage.setSigningDetails(args.mSigningDetails);
-        } else {
-            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-            final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
-                    input, parsedPackage, false /*skipVerify*/);
-            if (result.isError()) {
-                throw new PrepareFailure("Failed collect during installPackageLI",
-                        result.getException());
-            }
-            parsedPackage.setSigningDetails(result.getResult());
-        }
-
-        if (instantApp && parsedPackage.getSigningDetails().getSignatureSchemeVersion()
-                < SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2) {
-            Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
-                    + " is not signed with at least APK Signature Scheme v2");
-            throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
-                    "Instant app package must be signed with APK Signature Scheme v2 or greater");
-        }
-
-        boolean systemApp = false;
-        boolean replace = false;
-        synchronized (mPm.mLock) {
-            // Check if installing already existing package
-            if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
-                String oldName = mPm.mSettings.getRenamedPackageLPr(pkgName);
-                if (parsedPackage.getOriginalPackages().contains(oldName)
-                        && mPm.mPackages.containsKey(oldName)) {
-                    // This package is derived from an original package,
-                    // and this device has been updating from that original
-                    // name.  We must continue using the original name, so
-                    // rename the new package here.
-                    parsedPackage.setPackageName(oldName);
-                    pkgName = parsedPackage.getPackageName();
-                    replace = true;
-                    if (DEBUG_INSTALL) {
-                        Slog.d(TAG, "Replacing existing renamed package: oldName="
-                                + oldName + " pkgName=" + pkgName);
-                    }
-                } else if (mPm.mPackages.containsKey(pkgName)) {
-                    // This package, under its official name, already exists
-                    // on the device; we should replace it.
-                    replace = true;
-                    if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);
-                }
-
-                if (replace) {
-                    // Prevent apps opting out from runtime permissions
-                    AndroidPackage oldPackage = mPm.mPackages.get(pkgName);
-                    final int oldTargetSdk = oldPackage.getTargetSdkVersion();
-                    final int newTargetSdk = parsedPackage.getTargetSdkVersion();
-                    if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
-                            && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
-                        throw new PrepareFailure(
-                                PackageManager.INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE,
-                                "Package " + parsedPackage.getPackageName()
-                                        + " new target SDK " + newTargetSdk
-                                        + " doesn't support runtime permissions but the old"
-                                        + " target SDK " + oldTargetSdk + " does.");
-                    }
-                    // Prevent persistent apps from being updated
-                    if (oldPackage.isPersistent()
-                            && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
-                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
-                                "Package " + oldPackage.getPackageName() + " is a persistent app. "
-                                        + "Persistent apps are not updateable.");
-                    }
-                }
-            }
-
-            PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
-            if (ps != null) {
-                if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
-
-                // Static shared libs have same package with different versions where
-                // we internally use a synthetic package name to allow multiple versions
-                // of the same package, therefore we need to compare signatures against
-                // the package setting for the latest library version.
-                PackageSetting signatureCheckPs = ps;
-                if (parsedPackage.isStaticSharedLibrary()) {
-                    SharedLibraryInfo libraryInfo = mPm.getLatestSharedLibraVersionLPr(
-                            parsedPackage);
-                    if (libraryInfo != null) {
-                        signatureCheckPs = mPm.mSettings.getPackageLPr(
-                                libraryInfo.getPackageName());
-                    }
-                }
-
-                // Quick validity check that we're signed correctly if updating;
-                // we'll check this again later when scanning, but we want to
-                // bail early here before tripping over redefined permissions.
-                final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
-                if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
-                    if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
-                        throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
-                                + parsedPackage.getPackageName() + " upgrade keys do not match the "
-                                + "previously installed version");
-                    }
-                } else {
-                    try {
-                        final boolean compareCompat =
-                                mInstallPackageHelper.isCompatSignatureUpdateNeeded(parsedPackage);
-                        final boolean compareRecover =
-                                mInstallPackageHelper.isRecoverSignatureUpdateNeeded(parsedPackage);
-                        // We don't care about disabledPkgSetting on install for now.
-                        final boolean compatMatch = verifySignatures(signatureCheckPs, null,
-                                parsedPackage.getSigningDetails(), compareCompat, compareRecover,
-                                isRollback);
-                        // The new KeySets will be re-added later in the scanning process.
-                        if (compatMatch) {
-                            synchronized (mPm.mLock) {
-                                ksms.removeAppKeySetDataLPw(parsedPackage.getPackageName());
-                            }
-                        }
-                    } catch (PackageManagerException e) {
-                        throw new PrepareFailure(e.error, e.getMessage());
-                    }
-                }
-
-                if (ps.getPkg() != null) {
-                    systemApp = ps.getPkg().isSystem();
-                }
-                res.mOrigUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true);
-            }
-
-            final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups());
-            for (int groupNum = 0; groupNum < numGroups; groupNum++) {
-                final ParsedPermissionGroup group =
-                        parsedPackage.getPermissionGroups().get(groupNum);
-                final PermissionGroupInfo sourceGroup = mPm.getPermissionGroupInfo(group.getName(),
-                        0);
-
-                if (sourceGroup != null && cannotInstallWithBadPermissionGroups(parsedPackage)) {
-                    final String sourcePackageName = sourceGroup.packageName;
-
-                    if ((replace || !parsedPackage.getPackageName().equals(sourcePackageName))
-                            && !doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
-                            scanFlags)) {
-                        EventLog.writeEvent(0x534e4554, "146211400", -1,
-                                parsedPackage.getPackageName());
-
-                        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP,
-                                "Package "
-                                        + parsedPackage.getPackageName()
-                                        + " attempting to redeclare permission group "
-                                        + group.getName() + " already owned by "
-                                        + sourcePackageName);
-                    }
-                }
-            }
-
-            // TODO: Move logic for checking permission compatibility into PermissionManagerService
-            final int n = ArrayUtils.size(parsedPackage.getPermissions());
-            for (int i = n - 1; i >= 0; i--) {
-                final ParsedPermission perm = parsedPackage.getPermissions().get(i);
-                final Permission bp = mPm.mPermissionManager.getPermissionTEMP(perm.getName());
-
-                // Don't allow anyone but the system to define ephemeral permissions.
-                if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
-                        && !systemApp) {
-                    Slog.w(TAG, "Non-System package " + parsedPackage.getPackageName()
-                            + " attempting to delcare ephemeral permission "
-                            + perm.getName() + "; Removing ephemeral.");
-                    ComponentMutateUtils.setProtectionLevel(perm,
-                            perm.getProtectionLevel() & ~PermissionInfo.PROTECTION_FLAG_INSTANT);
-                }
-
-                // Check whether the newly-scanned package wants to define an already-defined perm
-                if (bp != null) {
-                    final String sourcePackageName = bp.getPackageName();
-
-                    if (!doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
-                            scanFlags)) {
-                        // If the owning package is the system itself, we log but allow
-                        // install to proceed; we fail the install on all other permission
-                        // redefinitions.
-                        if (!sourcePackageName.equals("android")) {
-                            throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION,
-                                    "Package "
-                                    + parsedPackage.getPackageName()
-                                    + " attempting to redeclare permission "
-                                    + perm.getName() + " already owned by "
-                                    + sourcePackageName)
-                                    .conflictsWithExistingPermission(perm.getName(),
-                                            sourcePackageName);
-                        } else {
-                            Slog.w(TAG, "Package " + parsedPackage.getPackageName()
-                                    + " attempting to redeclare system permission "
-                                    + perm.getName() + "; ignoring new declaration");
-                            parsedPackage.removePermission(i);
-                        }
-                    } else if (!PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())) {
-                        // Prevent apps to change protection level to dangerous from any other
-                        // type as this would allow a privilege escalation where an app adds a
-                        // normal/signature permission in other app's group and later redefines
-                        // it as dangerous leading to the group auto-grant.
-                        if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE)
-                                == PermissionInfo.PROTECTION_DANGEROUS) {
-                            if (bp != null && !bp.isRuntime()) {
-                                Slog.w(TAG, "Package " + parsedPackage.getPackageName()
-                                        + " trying to change a non-runtime permission "
-                                        + perm.getName()
-                                        + " to runtime; keeping old protection level");
-                                ComponentMutateUtils.setProtectionLevel(perm,
-                                        bp.getProtectionLevel());
-                            }
-                        }
-                    }
-                }
-
-                if (perm.getGroup() != null
-                        && cannotInstallWithBadPermissionGroups(parsedPackage)) {
-                    boolean isPermGroupDefinedByPackage = false;
-                    for (int groupNum = 0; groupNum < numGroups; groupNum++) {
-                        if (parsedPackage.getPermissionGroups().get(groupNum).getName()
-                                .equals(perm.getGroup())) {
-                            isPermGroupDefinedByPackage = true;
-                            break;
-                        }
-                    }
-
-                    if (!isPermGroupDefinedByPackage) {
-                        final PermissionGroupInfo sourceGroup =
-                                mPm.getPermissionGroupInfo(perm.getGroup(), 0);
-
-                        if (sourceGroup == null) {
-                            EventLog.writeEvent(0x534e4554, "146211400", -1,
-                                    parsedPackage.getPackageName());
-
-                            throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP,
-                                    "Package "
-                                            + parsedPackage.getPackageName()
-                                            + " attempting to declare permission "
-                                            + perm.getName() + " in non-existing group "
-                                            + perm.getGroup());
-                        } else {
-                            String groupSourcePackageName = sourceGroup.packageName;
-
-                            if (!PLATFORM_PACKAGE_NAME.equals(groupSourcePackageName)
-                                    && !doesSignatureMatchForPermissions(groupSourcePackageName,
-                                    parsedPackage, scanFlags)) {
-                                EventLog.writeEvent(0x534e4554, "146211400", -1,
-                                        parsedPackage.getPackageName());
-
-                                throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP,
-                                        "Package "
-                                                + parsedPackage.getPackageName()
-                                                + " attempting to declare permission "
-                                                + perm.getName() + " in group "
-                                                + perm.getGroup() + " owned by package "
-                                                + groupSourcePackageName
-                                                + " with incompatible certificate");
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        if (systemApp) {
-            if (onExternal) {
-                // Abort update; system app can't be replaced with app on sdcard
-                throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
-                        "Cannot install updates to system apps on sdcard");
-            } else if (instantApp) {
-                // Abort update; system app can't be replaced with an instant app
-                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
-                        "Cannot update a system app with an instant app");
-            }
-        }
-
-        if (args.mMoveInfo != null) {
-            // We did an in-place move, so dex is ready to roll
-            scanFlags |= SCAN_NO_DEX;
-            scanFlags |= SCAN_MOVE;
-
-            synchronized (mPm.mLock) {
-                final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
-                if (ps == null) {
-                    res.setError(INSTALL_FAILED_INTERNAL_ERROR,
-                            "Missing settings for moved package " + pkgName);
-                }
-
-                // We moved the entire application as-is, so bring over the
-                // previously derived ABI information.
-                parsedPackage.setPrimaryCpuAbi(ps.getPrimaryCpuAbi())
-                        .setSecondaryCpuAbi(ps.getSecondaryCpuAbi());
-            }
-
-        } else {
-            // Enable SCAN_NO_DEX flag to skip dexopt at a later stage
-            scanFlags |= SCAN_NO_DEX;
-
-            try {
-                PackageSetting pkgSetting;
-                AndroidPackage oldPackage;
-                synchronized (mPm.mLock) {
-                    pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
-                    oldPackage = mPm.mPackages.get(pkgName);
-                }
-                boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
-                        && pkgSetting.getPkgState().isUpdatedSystemApp();
-                final String abiOverride = deriveAbiOverride(args.mAbiOverride);
-                boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
-                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
-                        derivedAbi = mPm.mInjector.getAbiHelper().derivePackageAbi(parsedPackage,
-                        isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
-                        abiOverride, mPm.mAppLib32InstallDir);
-                derivedAbi.first.applyTo(parsedPackage);
-                derivedAbi.second.applyTo(parsedPackage);
-            } catch (PackageManagerException pme) {
-                Slog.e(TAG, "Error deriving application ABI", pme);
-                throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
-                        "Error deriving application ABI: " + pme.getMessage());
-            }
-        }
-
-        if (!args.doRename(res.mReturnCode, parsedPackage)) {
-            throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
-        }
-
-        try {
-            setUpFsVerityIfPossible(parsedPackage);
-        } catch (Installer.InstallerException | IOException | DigestException
-                | NoSuchAlgorithmException e) {
-            throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
-                    "Failed to set up verity: " + e);
-        }
-
-        final PackageFreezer freezer =
-                freezePackageForInstall(pkgName, installFlags, "installPackageLI");
-        boolean shouldCloseFreezerBeforeReturn = true;
-        try {
-            final AndroidPackage oldPackage;
-            String renamedPackage;
-            boolean sysPkg = false;
-            int targetScanFlags = scanFlags;
-            int targetParseFlags = parseFlags;
-            final PackageSetting ps;
-            final PackageSetting disabledPs;
-            if (replace) {
-                final String pkgName11 = parsedPackage.getPackageName();
-                synchronized (mPm.mLock) {
-                    oldPackage = mPm.mPackages.get(pkgName11);
-                }
-                if (parsedPackage.isStaticSharedLibrary()) {
-                    // Static libs have a synthetic package name containing the version
-                    // and cannot be updated as an update would get a new package name,
-                    // unless this is installed from adb which is useful for development.
-                    if (oldPackage != null
-                            && (installFlags & PackageManager.INSTALL_FROM_ADB) == 0) {
-                        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PACKAGE,
-                            "Packages declaring "
-                                + "static-shared libs cannot be updated");
-                    }
-                }
-
-                final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
-
-                final int[] allUsers;
-                final int[] installedUsers;
-                final int[] uninstalledUsers;
-
-                synchronized (mPm.mLock) {
-                    if (DEBUG_INSTALL) {
-                        Slog.d(TAG,
-                                "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage);
-                    }
-
-                    ps = mPm.mSettings.getPackageLPr(pkgName11);
-                    disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps);
-
-                    // verify signatures are valid
-                    final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
-                    if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) {
-                        if (!ksms.checkUpgradeKeySetLocked(ps, parsedPackage)) {
-                            throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                    "New package not signed by keys specified by upgrade-keysets: "
-                                            + pkgName11);
-                        }
-                    } else {
-                        SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails();
-                        SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails();
-                        // default to original signature matching
-                        if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails,
-                                SigningDetails.CertCapabilities.INSTALLED_DATA)
-                                && !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails,
-                                SigningDetails.CertCapabilities.ROLLBACK)) {
-                            // Allow the update to proceed if this is a rollback and the parsed
-                            // package's current signing key is the current signer or in the lineage
-                            // of the old package; this allows a rollback to a previously installed
-                            // version after an app's signing key has been rotated without requiring
-                            // the rollback capability on the previous signing key.
-                            if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
-                                    parsedPkgSigningDetails)) {
-                                throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                        "New package has a different signature: " + pkgName11);
-                            }
-                        }
-                    }
-
-                    // don't allow a system upgrade unless the upgrade hash matches
-                    if (oldPackage.getRestrictUpdateHash() != null && oldPackage.isSystem()) {
-                        final byte[] digestBytes;
-                        try {
-                            final MessageDigest digest = MessageDigest.getInstance("SHA-512");
-                            updateDigest(digest, new File(parsedPackage.getBaseApkPath()));
-                            if (!ArrayUtils.isEmpty(parsedPackage.getSplitCodePaths())) {
-                                for (String path : parsedPackage.getSplitCodePaths()) {
-                                    updateDigest(digest, new File(path));
-                                }
-                            }
-                            digestBytes = digest.digest();
-                        } catch (NoSuchAlgorithmException | IOException e) {
-                            throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
-                                    "Could not compute hash: " + pkgName11);
-                        }
-                        if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
-                            throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
-                                    "New package fails restrict-update check: " + pkgName11);
-                        }
-                        // retain upgrade restriction
-                        parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
-                    }
-
-                    // Check for shared user id changes
-                    if (!Objects.equals(oldPackage.getSharedUserId(),
-                            parsedPackage.getSharedUserId())
-                            // Don't mark as invalid if the app is trying to
-                            // leave a sharedUserId
-                            && parsedPackage.getSharedUserId() != null) {
-                        throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
-                                "Package " + parsedPackage.getPackageName()
-                                        + " shared user changed from "
-                                        + (oldPackage.getSharedUserId() != null
-                                        ? oldPackage.getSharedUserId() : "<nothing>")
-                                        + " to " + parsedPackage.getSharedUserId());
-                    }
-
-                    // In case of rollback, remember per-user/profile install state
-                    allUsers = mPm.mUserManager.getUserIds();
-                    installedUsers = ps.queryInstalledUsers(allUsers, true);
-                    uninstalledUsers = ps.queryInstalledUsers(allUsers, false);
-
-
-                    // don't allow an upgrade from full to ephemeral
-                    if (isInstantApp) {
-                        if (args.mUser == null
-                                || args.mUser.getIdentifier() == UserHandle.USER_ALL) {
-                            for (int currentUser : allUsers) {
-                                if (!ps.getInstantApp(currentUser)) {
-                                    // can't downgrade from full to instant
-                                    Slog.w(TAG,
-                                            "Can't replace full app with instant app: " + pkgName11
-                                                    + " for user: " + currentUser);
-                                    throw new PrepareFailure(
-                                            PackageManager.INSTALL_FAILED_SESSION_INVALID);
-                                }
-                            }
-                        } else if (!ps.getInstantApp(args.mUser.getIdentifier())) {
-                            // can't downgrade from full to instant
-                            Slog.w(TAG, "Can't replace full app with instant app: " + pkgName11
-                                    + " for user: " + args.mUser.getIdentifier());
-                            throw new PrepareFailure(
-                                    PackageManager.INSTALL_FAILED_SESSION_INVALID);
-                        }
-                    }
-                }
-
-                // Update what is removed
-                res.mRemovedInfo = new PackageRemovedInfo(mPm);
-                res.mRemovedInfo.mUid = oldPackage.getUid();
-                res.mRemovedInfo.mRemovedPackage = oldPackage.getPackageName();
-                res.mRemovedInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName;
-                res.mRemovedInfo.mIsStaticSharedLib =
-                        parsedPackage.getStaticSharedLibName() != null;
-                res.mRemovedInfo.mIsUpdate = true;
-                res.mRemovedInfo.mOrigUsers = installedUsers;
-                res.mRemovedInfo.mInstallReasons = new SparseArray<>(installedUsers.length);
-                for (int i = 0; i < installedUsers.length; i++) {
-                    final int userId = installedUsers[i];
-                    res.mRemovedInfo.mInstallReasons.put(userId, ps.getInstallReason(userId));
-                }
-                res.mRemovedInfo.mUninstallReasons = new SparseArray<>(uninstalledUsers.length);
-                for (int i = 0; i < uninstalledUsers.length; i++) {
-                    final int userId = uninstalledUsers[i];
-                    res.mRemovedInfo.mUninstallReasons.put(userId, ps.getUninstallReason(userId));
-                }
-
-                sysPkg = oldPackage.isSystem();
-                if (sysPkg) {
-                    // Set the system/privileged/oem/vendor/product flags as needed
-                    final boolean privileged = oldPackage.isPrivileged();
-                    final boolean oem = oldPackage.isOem();
-                    final boolean vendor = oldPackage.isVendor();
-                    final boolean product = oldPackage.isProduct();
-                    final boolean odm = oldPackage.isOdm();
-                    final boolean systemExt = oldPackage.isSystemExt();
-                    final @ParsingPackageUtils.ParseFlags int systemParseFlags = parseFlags;
-                    final @PackageManagerService.ScanFlags int systemScanFlags = scanFlags
-                            | SCAN_AS_SYSTEM
-                            | (privileged ? SCAN_AS_PRIVILEGED : 0)
-                            | (oem ? SCAN_AS_OEM : 0)
-                            | (vendor ? SCAN_AS_VENDOR : 0)
-                            | (product ? SCAN_AS_PRODUCT : 0)
-                            | (odm ? SCAN_AS_ODM : 0)
-                            | (systemExt ? SCAN_AS_SYSTEM_EXT : 0);
-
-                    if (DEBUG_INSTALL) {
-                        Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
-                                + ", old=" + oldPackage);
-                    }
-                    res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
-                    targetParseFlags = systemParseFlags;
-                    targetScanFlags = systemScanFlags;
-                } else { // non system replace
-                    replace = true;
-                    if (DEBUG_INSTALL) {
-                        Slog.d(TAG,
-                                "replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
-                                        + oldPackage);
-                    }
-                }
-            } else { // new package install
-                ps = null;
-                disabledPs = null;
-                replace = false;
-                oldPackage = null;
-                // Remember this for later, in case we need to rollback this install
-                String pkgName1 = parsedPackage.getPackageName();
-
-                if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + parsedPackage);
-
-                // TODO(b/194319951): MOVE TO RECONCILE
-                synchronized (mPm.mLock) {
-                    renamedPackage = mPm.mSettings.getRenamedPackageLPr(pkgName1);
-                    if (renamedPackage != null) {
-                        // A package with the same name is already installed, though
-                        // it has been renamed to an older name.  The package we
-                        // are trying to install should be installed as an update to
-                        // the existing one, but that has not been requested, so bail.
-                        throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS,
-                                "Attempt to re-install " + pkgName1
-                                        + " without first uninstalling package running as "
-                                        + renamedPackage);
-                    }
-                    if (mPm.mPackages.containsKey(pkgName1)) {
-                        // Don't allow installation over an existing package with the same name.
-                        throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS,
-                                "Attempt to re-install " + pkgName1
-                                        + " without first uninstalling.");
-                    }
-                }
-            }
-            // we're passing the freezer back to be closed in a later phase of install
-            shouldCloseFreezerBeforeReturn = false;
-
-            return new PrepareResult(replace, targetScanFlags, targetParseFlags,
-                oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg,
-                    ps, disabledPs);
-        } finally {
-            res.mFreezer = freezer;
-            if (shouldCloseFreezerBeforeReturn) {
-                freezer.close();
-            }
-        }
-    }
-
-    /*
-     * Cannot properly check CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS using CompatChanges
-     * as this only works for packages that are installed
-     *
-     * TODO: Move logic for permission group compatibility into PermissionManagerService
-     */
-    @SuppressWarnings("AndroidFrameworkCompatChange")
-    private static boolean cannotInstallWithBadPermissionGroups(ParsedPackage parsedPackage) {
-        return parsedPackage.getTargetSdkVersion() >= Build.VERSION_CODES.S;
-    }
-
-    private boolean doesSignatureMatchForPermissions(@NonNull String sourcePackageName,
-            @NonNull ParsedPackage parsedPackage, int scanFlags) {
-        // If the defining package is signed with our cert, it's okay.  This
-        // also includes the "updating the same package" case, of course.
-        // "updating same package" could also involve key-rotation.
-
-        final PackageSetting sourcePackageSetting;
-        final KeySetManagerService ksms;
-        synchronized (mPm.mLock) {
-            sourcePackageSetting = mPm.mSettings.getPackageLPr(sourcePackageName);
-            ksms = mPm.mSettings.getKeySetManagerService();
-        }
-
-        final SigningDetails sourceSigningDetails = (sourcePackageSetting == null
-                ? SigningDetails.UNKNOWN : sourcePackageSetting.getSigningDetails());
-        if (sourcePackageName.equals(parsedPackage.getPackageName())
-                && (ksms.shouldCheckUpgradeKeySetLocked(
-                sourcePackageSetting, scanFlags))) {
-            return ksms.checkUpgradeKeySetLocked(sourcePackageSetting, parsedPackage);
-        } else {
-
-            // in the event of signing certificate rotation, we need to see if the
-            // package's certificate has rotated from the current one, or if it is an
-            // older certificate with which the current is ok with sharing permissions
-            if (sourceSigningDetails.checkCapability(
-                    parsedPackage.getSigningDetails(),
-                    SigningDetails.CertCapabilities.PERMISSION)) {
-                return true;
-            } else if (parsedPackage.getSigningDetails().checkCapability(
-                    sourceSigningDetails,
-                    SigningDetails.CertCapabilities.PERMISSION)) {
-                // the scanned package checks out, has signing certificate rotation
-                // history, and is newer; bring it over
-                synchronized (mPm.mLock) {
-                    sourcePackageSetting.setSigningDetails(parsedPackage.getSigningDetails());
-                }
-                return true;
-            } else {
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Set up fs-verity for the given package if possible.  This requires a feature flag of system
-     * property to be enabled only if the kernel supports fs-verity.
-     *
-     * <p>When the feature flag is set to legacy mode, only APK is supported (with some experimental
-     * kernel patches). In normal mode, all file format can be supported.
-     */
-    private void setUpFsVerityIfPossible(AndroidPackage pkg) throws Installer.InstallerException,
-            PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
-        final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
-        final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
-        if (!standardMode && !legacyMode) {
-            return;
-        }
-
-        if (isIncrementalPath(pkg.getPath()) && IncrementalManager.getVersion()
-                < IncrementalManager.MIN_VERSION_TO_SUPPORT_FSVERITY) {
-            return;
-        }
-
-        // Collect files we care for fs-verity setup.
-        ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
-        if (legacyMode) {
-            synchronized (mPm.mLock) {
-                final PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
-                if (ps != null && ps.isPrivileged()) {
-                    fsverityCandidates.put(pkg.getBaseApkPath(), null);
-                    if (pkg.getSplitCodePaths() != null) {
-                        for (String splitPath : pkg.getSplitCodePaths()) {
-                            fsverityCandidates.put(splitPath, null);
-                        }
-                    }
-                }
-            }
-        } else {
-            // NB: These files will become only accessible if the signing key is loaded in kernel's
-            // .fs-verity keyring.
-            fsverityCandidates.put(pkg.getBaseApkPath(),
-                    VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
-
-            final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
-                    pkg.getBaseApkPath());
-            if (new File(dmPath).exists()) {
-                fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
-            }
-
-            if (pkg.getSplitCodePaths() != null) {
-                for (String path : pkg.getSplitCodePaths()) {
-                    fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
-
-                    final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
-                    if (new File(splitDmPath).exists()) {
-                        fsverityCandidates.put(splitDmPath,
-                                VerityUtils.getFsveritySignatureFilePath(splitDmPath));
-                    }
-                }
-            }
-        }
-
-        for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
-            final String filePath = entry.getKey();
-            final String signaturePath = entry.getValue();
-
-            if (!legacyMode) {
-                // fs-verity is optional for now.  Only set up if signature is provided.
-                if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
-                    try {
-                        VerityUtils.setUpFsverity(filePath, signaturePath);
-                    } catch (IOException e) {
-                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
-                                "Failed to enable fs-verity: " + e);
-                    }
-                }
-                continue;
-            }
-
-            // In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
-            final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(filePath);
-            if (result.isOk()) {
-                if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling verity to " + filePath);
-                final FileDescriptor fd = result.getUnownedFileDescriptor();
-                try {
-                    final byte[] rootHash = VerityUtils.generateApkVerityRootHash(filePath);
-                    try {
-                        // A file may already have fs-verity, e.g. when reused during a split
-                        // install. If the measurement succeeds, no need to attempt to set up.
-                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
-                    } catch (Installer.InstallerException e) {
-                        mPm.mInstaller.installApkVerity(filePath, fd, result.getContentSize());
-                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
-                    }
-                } finally {
-                    IoUtils.closeQuietly(fd);
-                }
-            } else if (result.isFailed()) {
-                throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
-                        "Failed to generate verity");
-            }
-        }
-    }
-
-    private PackageFreezer freezePackageForInstall(String packageName, int installFlags,
-            String killReason) {
-        return freezePackageForInstall(packageName, UserHandle.USER_ALL, installFlags, killReason);
-    }
-
-    private PackageFreezer freezePackageForInstall(String packageName, int userId, int installFlags,
-            String killReason) {
-        if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
-            return new PackageFreezer(mPm);
-        } else {
-            return mPm.freezePackage(packageName, userId, killReason);
-        }
-    }
-
-    private static void updateDigest(MessageDigest digest, File file) throws IOException {
-        try (DigestInputStream digestStream =
-                     new DigestInputStream(new FileInputStream(file), digest)) {
-            int length, total = 0;
-            while ((length = digestStream.read()) != -1) {
-                total += length;
-            } // just plow through the file
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void commitPackagesLocked(final CommitRequest request) {
-        // TODO: remove any expected failures from this method; this should only be able to fail due
-        //       to unavoidable errors (I/O, etc.)
-        for (ReconciledPackage reconciledPkg : request.mReconciledPackages.values()) {
-            final ScanResult scanResult = reconciledPkg.mScanResult;
-            final ScanRequest scanRequest = scanResult.mRequest;
-            final ParsedPackage parsedPackage = scanRequest.mParsedPackage;
-            final String packageName = parsedPackage.getPackageName();
-            final PackageInstalledInfo res = reconciledPkg.mInstallResult;
-            final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
-            final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
-
-            if (reconciledPkg.mPrepareResult.mReplace) {
-                AndroidPackage oldPackage = mPm.mPackages.get(packageName);
-
-                // Set the update and install times
-                PackageStateInternal deletedPkgSetting = mPm.getPackageStateInternal(
-                        oldPackage.getPackageName());
-                reconciledPkg.mPkgSetting
-                        .setFirstInstallTime(deletedPkgSetting.getFirstInstallTime())
-                        .setLastUpdateTime(System.currentTimeMillis());
-
-                res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
-                        reconciledPkg.mPkgSetting, request.mAllUsers,
-                        mPm.mSettings.getPackagesLocked());
-                if (reconciledPkg.mPrepareResult.mSystem) {
-                    // Remove existing system package
-                    removePackageHelper.removePackageLI(oldPackage, true);
-                    if (!disableSystemPackageLPw(oldPackage)) {
-                        // We didn't need to disable the .apk as a current system package,
-                        // which means we are replacing another update that is already
-                        // installed.  We need to make sure to delete the older one's .apk.
-                        res.mRemovedInfo.mArgs = new FileInstallArgs(
-                                oldPackage.getPath(),
-                                getAppDexInstructionSets(
-                                        AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
-                                                deletedPkgSetting),
-                                        AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,
-                                                deletedPkgSetting)), mPm);
-                    } else {
-                        res.mRemovedInfo.mArgs = null;
-                    }
-                } else {
-                    try {
-                        // Settings will be written during the call to updateSettingsLI().
-                        deletePackageHelper.executeDeletePackageLIF(
-                                reconciledPkg.mDeletePackageAction, packageName,
-                                true, request.mAllUsers, false);
-                    } catch (SystemDeleteException e) {
-                        if (mPm.mIsEngBuild) {
-                            throw new RuntimeException("Unexpected failure", e);
-                            // ignore; not possible for non-system app
-                        }
-                    }
-                    // Successfully deleted the old package; proceed with replace.
-
-                    // If deleted package lived in a container, give users a chance to
-                    // relinquish resources before killing.
-                    if (oldPackage.isExternalStorage()) {
-                        if (DEBUG_INSTALL) {
-                            Slog.i(TAG, "upgrading pkg " + oldPackage
-                                    + " is ASEC-hosted -> UNAVAILABLE");
-                        }
-                        final int[] uidArray = new int[]{oldPackage.getUid()};
-                        final ArrayList<String> pkgList = new ArrayList<>(1);
-                        pkgList.add(oldPackage.getPackageName());
-                        mBroadcastHelper.sendResourcesChangedBroadcast(
-                                false, true, pkgList, uidArray, null);
-                    }
-
-                    // Update the in-memory copy of the previous code paths.
-                    PackageSetting ps1 = mPm.mSettings.getPackageLPr(
-                            reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
-                    if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP)
-                            == 0) {
-                        if (ps1.mOldCodePaths == null) {
-                            ps1.mOldCodePaths = new ArraySet<>();
-                        }
-                        Collections.addAll(ps1.mOldCodePaths, oldPackage.getBaseApkPath());
-                        if (oldPackage.getSplitCodePaths() != null) {
-                            Collections.addAll(ps1.mOldCodePaths, oldPackage.getSplitCodePaths());
-                        }
-                    } else {
-                        ps1.mOldCodePaths = null;
-                    }
-
-                    if (reconciledPkg.mInstallResult.mReturnCode
-                            == PackageManager.INSTALL_SUCCEEDED) {
-                        PackageSetting ps2 = mPm.mSettings.getPackageLPr(
-                                parsedPackage.getPackageName());
-                        if (ps2 != null) {
-                            res.mRemovedInfo.mRemovedForAllUsers =
-                                    mPm.mPackages.get(ps2.getPackageName()) == null;
-                        }
-                    }
-                }
-            }
-
-            AndroidPackage pkg = mInstallPackageHelper.commitReconciledScanResultLocked(
-                    reconciledPkg, request.mAllUsers);
-            updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res);
-
-            final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
-            if (ps != null) {
-                res.mNewUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true);
-                ps.setUpdateAvailable(false /*updateAvailable*/);
-            }
-            if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED) {
-                mPm.updateSequenceNumberLP(ps, res.mNewUsers);
-                mPm.updateInstantAppInstallerLocked(packageName);
-            }
-        }
-        ApplicationPackageManager.invalidateGetPackagesForUidCache();
-    }
-
-    @GuardedBy("mLock")
-    private boolean disableSystemPackageLPw(AndroidPackage oldPkg) {
-        return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true);
-    }
-
-    private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg,
-            int[] allUsers, PackageInstalledInfo res) {
-        updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, res);
-    }
-
-    private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg,
-            int[] allUsers, PackageInstalledInfo res) {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
-
-        final String pkgName = pkg.getPackageName();
-        final int[] installedForUsers = res.mOrigUsers;
-        final InstallArgs installArgs = reconciledPkg.mInstallArgs;
-        final int installReason = installArgs.mInstallReason;
-        InstallSource installSource = installArgs.mInstallSource;
-        final String installerPackageName = installSource.installerPackageName;
-
-        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
-        synchronized (mPm.mLock) {
-            // For system-bundled packages, we assume that installing an upgraded version
-            // of the package implies that the user actually wants to run that new code,
-            // so we enable the package.
-            final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
-            final int userId = installArgs.mUser.getIdentifier();
-            if (ps != null) {
-                if (pkg.isSystem()) {
-                    if (DEBUG_INSTALL) {
-                        Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
-                    }
-                    // Enable system package for requested users
-                    if (res.mOrigUsers != null) {
-                        for (int origUserId : res.mOrigUsers) {
-                            if (userId == UserHandle.USER_ALL || userId == origUserId) {
-                                ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
-                                        origUserId, installerPackageName);
-                            }
-                        }
-                    }
-                    // Also convey the prior install/uninstall state
-                    if (allUsers != null && installedForUsers != null) {
-                        for (int currentUserId : allUsers) {
-                            final boolean installed = ArrayUtils.contains(
-                                    installedForUsers, currentUserId);
-                            if (DEBUG_INSTALL) {
-                                Slog.d(TAG, "    user " + currentUserId + " => " + installed);
-                            }
-                            ps.setInstalled(installed, currentUserId);
-                        }
-                        // these install state changes will be persisted in the
-                        // upcoming call to mSettings.writeLPr().
-                    }
-
-                    if (allUsers != null) {
-                        for (int currentUserId : allUsers) {
-                            ps.resetOverrideComponentLabelIcon(currentUserId);
-                        }
-                    }
-                }
-
-                // Retrieve the overlays for shared libraries of the package.
-                if (!ps.getPkgState().getUsesLibraryInfos().isEmpty()) {
-                    for (SharedLibraryInfo sharedLib : ps.getPkgState().getUsesLibraryInfos()) {
-                        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
-                            if (!sharedLib.isDynamic()) {
-                                // TODO(146804378): Support overlaying static shared libraries
-                                continue;
-                            }
-                            final PackageSetting libPs = mPm.mSettings.getPackageLPr(
-                                    sharedLib.getPackageName());
-                            if (libPs == null) {
-                                continue;
-                            }
-                            ps.setOverlayPathsForLibrary(sharedLib.getName(),
-                                    libPs.getOverlayPaths(currentUserId), currentUserId);
-                        }
-                    }
-                }
-
-                // It's implied that when a user requests installation, they want the app to be
-                // installed and enabled. (This does not apply to USER_ALL, which here means only
-                // install on users for which the app is already installed).
-                if (userId != UserHandle.USER_ALL) {
-                    ps.setInstalled(true, userId);
-                    ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
-                }
-
-                mPm.mSettings.addInstallerPackageNames(ps.getInstallSource());
-
-                // When replacing an existing package, preserve the original install reason for all
-                // users that had the package installed before. Similarly for uninstall reasons.
-                final Set<Integer> previousUserIds = new ArraySet<>();
-                if (res.mRemovedInfo != null && res.mRemovedInfo.mInstallReasons != null) {
-                    final int installReasonCount = res.mRemovedInfo.mInstallReasons.size();
-                    for (int i = 0; i < installReasonCount; i++) {
-                        final int previousUserId = res.mRemovedInfo.mInstallReasons.keyAt(i);
-                        final int previousInstallReason =
-                                res.mRemovedInfo.mInstallReasons.valueAt(i);
-                        ps.setInstallReason(previousInstallReason, previousUserId);
-                        previousUserIds.add(previousUserId);
-                    }
-                }
-                if (res.mRemovedInfo != null && res.mRemovedInfo.mUninstallReasons != null) {
-                    for (int i = 0; i < res.mRemovedInfo.mUninstallReasons.size(); i++) {
-                        final int previousUserId = res.mRemovedInfo.mUninstallReasons.keyAt(i);
-                        final int previousReason = res.mRemovedInfo.mUninstallReasons.valueAt(i);
-                        ps.setUninstallReason(previousReason, previousUserId);
-                    }
-                }
-
-                // Set install reason for users that are having the package newly installed.
-                final int[] allUsersList = mPm.mUserManager.getUserIds();
-                if (userId == UserHandle.USER_ALL) {
-                    // TODO(b/152629990): It appears that the package doesn't actually get newly
-                    //  installed in this case, so the installReason shouldn't get modified?
-                    for (int currentUserId : allUsersList) {
-                        if (!previousUserIds.contains(currentUserId)) {
-                            ps.setInstallReason(installReason, currentUserId);
-                        }
-                    }
-                } else if (!previousUserIds.contains(userId)) {
-                    ps.setInstallReason(installReason, userId);
-                }
-
-                // TODO(b/169721400): generalize Incremental States and create a Callback object
-                // that can be used for all the packages.
-                final String codePath = ps.getPathString();
-                if (IncrementalManager.isIncrementalPath(codePath)
-                        && mPm.mIncrementalManager != null) {
-                    mPm.mIncrementalManager.registerLoadingProgressCallback(codePath,
-                            new IncrementalProgressListener(ps.getPackageName(), mPm));
-                }
-
-                // Ensure that the uninstall reason is UNKNOWN for users with the package installed.
-                for (int currentUserId : allUsersList) {
-                    if (ps.getInstalled(currentUserId)) {
-                        ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, currentUserId);
-                    }
-                }
-
-                mPm.mSettings.writeKernelMappingLPr(ps);
-
-                final PermissionManagerServiceInternal.PackageInstalledParams.Builder
-                        permissionParamsBuilder =
-                        new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
-                final boolean grantPermissions = (installArgs.mInstallFlags
-                        & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0;
-                if (grantPermissions) {
-                    final List<String> grantedPermissions =
-                            installArgs.mInstallGrantPermissions != null
-                                    ? Arrays.asList(installArgs.mInstallGrantPermissions)
-                                    : pkg.getRequestedPermissions();
-                    permissionParamsBuilder.setGrantedPermissions(grantedPermissions);
-                }
-                final boolean allowlistAllRestrictedPermissions =
-                        (installArgs.mInstallFlags
-                                & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0;
-                final List<String> allowlistedRestrictedPermissions =
-                        allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions()
-                                : installArgs.mAllowlistedRestrictedPermissions;
-                if (allowlistedRestrictedPermissions != null) {
-                    permissionParamsBuilder.setAllowlistedRestrictedPermissions(
-                            allowlistedRestrictedPermissions);
-                }
-                final int autoRevokePermissionsMode = installArgs.mAutoRevokePermissionsMode;
-                permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
-                final ScanResult scanResult = reconciledPkg.mScanResult;
-                mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId,
-                        permissionParamsBuilder.build(), userId);
-            }
-            res.mName = pkgName;
-            res.mUid = pkg.getUid();
-            res.mPkg = pkg;
-            res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
-            //to update install status
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
-            mPm.writeSettingsLPrTEMP();
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-
-        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-    }
-
-    /**
-     * On successful install, executes remaining steps after commit completes and the package lock
-     * is released. These are typically more expensive or require calls to installd, which often
-     * locks on {@link com.android.server.pm.PackageManagerService.mLock}.
-     */
-    private void executePostCommitSteps(CommitRequest commitRequest) {
-        final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
-        final AppDataHelper appDataHelper = new AppDataHelper(mPm);
-        for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
-            final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags
-                    & SCAN_AS_INSTANT_APP) != 0);
-            final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
-            final String packageName = pkg.getPackageName();
-            final String codePath = pkg.getPath();
-            final boolean onIncremental = mPm.mIncrementalManager != null
-                    && isIncrementalPath(codePath);
-            if (onIncremental) {
-                IncrementalStorage storage = mPm.mIncrementalManager.openStorage(codePath);
-                if (storage == null) {
-                    throw new IllegalArgumentException(
-                            "Install: null storage for incremental package " + packageName);
-                }
-                incrementalStorages.add(storage);
-            }
-            int previousAppId = 0;
-            if (reconciledPkg.mScanResult.needsNewAppId()) {
-                // Only set previousAppId if the app is migrating out of shared UID
-                previousAppId = reconciledPkg.mScanResult.mPreviousAppId;
-            }
-            appDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId);
-            if (reconciledPkg.mPrepareResult.mClearCodeCache) {
-                appDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
-                        FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
-                                | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-            }
-            if (reconciledPkg.mPrepareResult.mReplace) {
-                mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
-                        pkg.getBaseApkPath(), pkg.getSplitCodePaths());
-            }
-
-            // Prepare the application profiles for the new code paths.
-            // This needs to be done before invoking dexopt so that any install-time profile
-            // can be used for optimizations.
-            mPm.mArtManagerService.prepareAppProfiles(
-                    pkg,
-                    mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()),
-                    /* updateReferenceProfileContent= */ true);
-
-            // Compute the compilation reason from the installation scenario.
-            final int compilationReason =
-                    mPm.getDexManager().getCompilationReasonForInstallScenario(
-                            reconciledPkg.mInstallArgs.mInstallScenario);
-
-            // Construct the DexoptOptions early to see if we should skip running dexopt.
-            //
-            // Do not run PackageDexOptimizer through the local performDexOpt
-            // method because `pkg` may not be in `mPackages` yet.
-            //
-            // Also, don't fail application installs if the dexopt step fails.
-            final boolean isBackupOrRestore =
-                    reconciledPkg.mInstallArgs.mInstallReason == INSTALL_REASON_DEVICE_RESTORE
-                            || reconciledPkg.mInstallArgs.mInstallReason
-                            == INSTALL_REASON_DEVICE_SETUP;
-
-            final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
-                    | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
-                    | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
-            DexoptOptions dexoptOptions =
-                    new DexoptOptions(packageName, compilationReason, dexoptFlags);
-
-            // Check whether we need to dexopt the app.
-            //
-            // NOTE: it is IMPORTANT to call dexopt:
-            //   - after doRename which will sync the package data from AndroidPackage and
-            //     its corresponding ApplicationInfo.
-            //   - after installNewPackageLIF or replacePackageLIF which will update result with the
-            //     uid of the application (pkg.applicationInfo.uid).
-            //     This update happens in place!
-            //
-            // We only need to dexopt if the package meets ALL of the following conditions:
-            //   1) it is not an instant app or if it is then dexopt is enabled via gservices.
-            //   2) it is not debuggable.
-            //   3) it is not on Incremental File System.
-            //
-            // Note that we do not dexopt instant apps by default. dexopt can take some time to
-            // complete, so we skip this step during installation. Instead, we'll take extra time
-            // the first time the instant app starts. It's preferred to do it this way to provide
-            // continuous progress to the useur instead of mysteriously blocking somewhere in the
-            // middle of running an instant app. The default behaviour can be overridden
-            // via gservices.
-            //
-            // Furthermore, dexopt may be skipped, depending on the install scenario and current
-            // state of the device.
-            //
-            // TODO(b/174695087): instantApp and onIncremental should be removed and their install
-            //       path moved to SCENARIO_FAST.
-            final boolean performDexopt =
-                    (!instantApp || android.provider.Settings.Global.getInt(
-                            mPm.mContext.getContentResolver(),
-                            android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
-                            && !pkg.isDebuggable()
-                            && (!onIncremental)
-                            && dexoptOptions.isCompilationEnabled();
-
-            if (performDexopt) {
-                // Compile the layout resources.
-                if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
-                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
-                    mPm.mViewCompiler.compileLayouts(pkg);
-                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                }
-
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-                ScanResult result = reconciledPkg.mScanResult;
-
-                // This mirrors logic from commitReconciledScanResultLocked, where the library files
-                // needed for dexopt are assigned.
-                // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous
-                //  setting needs to be passed to have a comparison, hide it behind an immutable
-                //  interface. There's no good reason to have 3 different ways to access the real
-                //  PackageSetting object, only one of which is actually correct.
-                PackageSetting realPkgSetting = result.mExistingSettingCopied
-                        ? result.mRequest.mPkgSetting : result.mPkgSetting;
-                if (realPkgSetting == null) {
-                    realPkgSetting = reconciledPkg.mPkgSetting;
-                }
-
-                // Unfortunately, the updated system app flag is only tracked on this PackageSetting
-                boolean isUpdatedSystemApp = reconciledPkg.mPkgSetting.getPkgState()
-                        .isUpdatedSystemApp();
-
-                realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
-
-                mPm.mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
-                        null /* instructionSets */,
-                        mPm.getOrCreateCompilerPackageStats(pkg),
-                        mPm.getDexManager().getPackageUseInfoOrDefault(packageName),
-                        dexoptOptions);
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-            }
-
-            // Notify BackgroundDexOptService that the package has been changed.
-            // If this is an update of a package which used to fail to compile,
-            // BackgroundDexOptService will remove it from its denylist.
-            // TODO: Layering violation
-            BackgroundDexOptService.getService().notifyPackageChanged(packageName);
-
-            notifyPackageChangeObserversOnUpdate(reconciledPkg);
-        }
-        waitForNativeBinariesExtraction(incrementalStorages);
-    }
-
-    private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) {
-        final PackageSetting pkgSetting = reconciledPkg.mPkgSetting;
-        final PackageInstalledInfo pkgInstalledInfo = reconciledPkg.mInstallResult;
-        final PackageRemovedInfo pkgRemovedInfo = pkgInstalledInfo.mRemovedInfo;
-
-        PackageChangeEvent pkgChangeEvent = new PackageChangeEvent();
-        pkgChangeEvent.packageName = pkgSetting.getPkg().getPackageName();
-        pkgChangeEvent.version = pkgSetting.getVersionCode();
-        pkgChangeEvent.lastUpdateTimeMillis = pkgSetting.getLastUpdateTime();
-        pkgChangeEvent.newInstalled = (pkgRemovedInfo == null || !pkgRemovedInfo.mIsUpdate);
-        pkgChangeEvent.dataRemoved = (pkgRemovedInfo != null && pkgRemovedInfo.mDataRemoved);
-        pkgChangeEvent.isDeleted = false;
-
-        mPm.notifyPackageChangeObservers(pkgChangeEvent);
-    }
-
-    static void waitForNativeBinariesExtraction(
-            ArraySet<IncrementalStorage> incrementalStorages) {
-        if (incrementalStorages.isEmpty()) {
-            return;
-        }
-        try {
-            // Native library extraction may take very long time: each page could potentially
-            // wait for either 10s or 100ms (adb vs non-adb data loader), and that easily adds
-            // up to a full watchdog timeout of 1 min, killing the system after that. It doesn't
-            // make much sense as blocking here doesn't lock up the framework, but only blocks
-            // the installation session and the following ones.
-            Watchdog.getInstance().pauseWatchingCurrentThread("native_lib_extract");
-            for (int i = 0; i < incrementalStorages.size(); ++i) {
-                IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i);
-                storage.waitForNativeBinariesExtraction();
-            }
-        } finally {
-            Watchdog.getInstance().resumeWatchingCurrentThread("native_lib_extract");
-        }
-    }
-
     /**
      * Ensure that the install reason matches what we know about the package installer (e.g. whether
      * it is acting on behalf on an enterprise or the user).
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index e3a772c..1a9c7a9 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -16,21 +16,16 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
-import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION;
-import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD;
 import static com.android.server.pm.PackageManagerService.DEFAULT_VERIFICATION_RESPONSE;
 import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_INSTALL_OBSERVER;
 import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_POST_DELETE;
-import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_POST_DELETE_DELAY_MS;
 import static com.android.server.pm.PackageManagerService.DOMAIN_VERIFICATION;
-import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_STATUS;
 import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT;
 import static com.android.server.pm.PackageManagerService.INIT_COPY;
@@ -48,15 +43,11 @@
 import static com.android.server.pm.PackageManagerService.WRITE_SETTINGS;
 
 import android.content.Intent;
-import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.InstantAppRequest;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -65,42 +56,22 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
-import android.stats.storage.StorageEnums;
-import android.util.ArrayMap;
-import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.EventLogTags;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
-
-import dalvik.system.VMRuntime;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 
 import java.io.IOException;
 
 /**
  * Part of PackageManagerService that handles events.
  */
-public final class PackageHandler extends Handler {
+final class PackageHandler extends Handler {
     private final PackageManagerService mPm;
-    private final VerificationHelper mVerificationHelper;
-    private final BroadcastHelper mBroadcastHelper;
+    private final InstallPackageHelper mInstallPackageHelper;
 
     PackageHandler(Looper looper, PackageManagerService pm) {
         super(looper);
         mPm = pm;
-        mVerificationHelper = new VerificationHelper(mPm.mContext);
-        mBroadcastHelper = new BroadcastHelper(mPm.mInjector);
+        mInstallPackageHelper = new InstallPackageHelper(mPm);
     }
 
     @Override
@@ -127,46 +98,7 @@
                 break;
             }
             case SEND_PENDING_BROADCAST: {
-                String[] packages;
-                ArrayList<String>[] components;
-                int size = 0;
-                int[] uids;
-                Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                synchronized (mPm.mLock) {
-                    size = mPm.mPendingBroadcasts.size();
-                    if (size <= 0) {
-                        // Nothing to be done. Just return
-                        return;
-                    }
-                    packages = new String[size];
-                    components = new ArrayList[size];
-                    uids = new int[size];
-                    int i = 0;  // filling out the above arrays
-
-                    for (int n = 0; n < mPm.mPendingBroadcasts.userIdCount(); n++) {
-                        final int packageUserId = mPm.mPendingBroadcasts.userIdAt(n);
-                        final ArrayMap<String, ArrayList<String>> componentsToBroadcast =
-                                mPm.mPendingBroadcasts.packagesForUserId(packageUserId);
-                        final int numComponents = componentsToBroadcast.size();
-                        for (int index = 0; i < size && index < numComponents; index++) {
-                            packages[i] = componentsToBroadcast.keyAt(index);
-                            components[i] = componentsToBroadcast.valueAt(index);
-                            final PackageSetting ps = mPm.mSettings.getPackageLPr(packages[i]);
-                            uids[i] = (ps != null)
-                                    ? UserHandle.getUid(packageUserId, ps.getAppId())
-                                    : -1;
-                            i++;
-                        }
-                    }
-                    size = i;
-                    mPm.mPendingBroadcasts.clear();
-                }
-                // Send broadcasts
-                for (int i = 0; i < size; i++) {
-                    mPm.sendPackageChangedBroadcast(packages[i], true /* dontKillApp */,
-                            components[i], uids[i], null /* reason */);
-                }
-                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                mInstallPackageHelper.sendPendingBroadcasts();
                 break;
             }
             case POST_INSTALL: {
@@ -183,23 +115,7 @@
                 if (data != null && data.mPostInstallRunnable != null) {
                     data.mPostInstallRunnable.run();
                 } else if (data != null && data.args != null) {
-                    InstallArgs args = data.args;
-                    PackageInstalledInfo parentRes = data.res;
-
-                    final boolean killApp = (args.mInstallFlags
-                            & PackageManager.INSTALL_DONT_KILL_APP) == 0;
-                    final boolean virtualPreload = ((args.mInstallFlags
-                            & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
-
-                    handlePackagePostInstall(parentRes, killApp, virtualPreload,
-                            didRestore, args.mInstallSource.installerPackageName,
-                            args.mObserver, args.mDataLoaderType);
-
-                    // Log tracing if needed
-                    if (args.mTraceMethod != null) {
-                        Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, args.mTraceMethod,
-                                args.mTraceCookie);
-                    }
+                    mInstallPackageHelper.handlePackagePostInstall(data.res, data.args, didRestore);
                 } else if (DEBUG_INSTALL) {
                     // No post-install when we run restore from installExistingPackageForUser
                     Slog.i(TAG, "Nothing to do for post-install token " + msg.arg1);
@@ -223,31 +139,17 @@
             } break;
             case WRITE_SETTINGS: {
                 Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                synchronized (mPm.mLock) {
-                    removeMessages(WRITE_SETTINGS);
-                    removeMessages(WRITE_PACKAGE_RESTRICTIONS);
-                    mPm.writeSettingsLPrTEMP();
-                    mPm.mDirtyUsers.clear();
-                }
+                mPm.writeSettings();
                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
             } break;
             case WRITE_PACKAGE_RESTRICTIONS: {
                 Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                synchronized (mPm.mLock) {
-                    removeMessages(WRITE_PACKAGE_RESTRICTIONS);
-                    for (int userId : mPm.mDirtyUsers) {
-                        mPm.mSettings.writePackageRestrictionsLPr(userId);
-                    }
-                    mPm.mDirtyUsers.clear();
-                }
+                mPm.writePendingRestrictions();
                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
             } break;
             case WRITE_PACKAGE_LIST: {
                 Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                synchronized (mPm.mLock) {
-                    removeMessages(WRITE_PACKAGE_LIST);
-                    mPm.mSettings.writePackageListLPr(msg.arg1);
-                }
+                mPm.writePackageList(msg.arg1);
                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
             } break;
             case CHECK_PENDING_VERIFICATION: {
@@ -268,13 +170,13 @@
                         Slog.i(TAG, "Continuing with installation of " + originUri);
                         state.setVerifierResponse(Binder.getCallingUid(),
                                 PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
-                        mVerificationHelper.broadcastPackageVerified(verificationId, originUri,
+                        VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                                 PackageManager.VERIFICATION_ALLOW, null, params.mDataLoaderType,
-                                user);
+                                user, mPm.mContext);
                     } else {
-                        mVerificationHelper.broadcastPackageVerified(verificationId, originUri,
+                        VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                                 PackageManager.VERIFICATION_REJECT, null,
-                                params.mDataLoaderType, user);
+                                params.mDataLoaderType, user, mPm.mContext);
                         params.setReturnCode(
                                 PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
                         state.setVerifierResponse(Binder.getCallingUid(),
@@ -349,8 +251,9 @@
                     final Uri originUri = Uri.fromFile(params.mOriginInfo.mResolvedFile);
 
                     if (state.isInstallAllowed()) {
-                        mVerificationHelper.broadcastPackageVerified(verificationId, originUri,
-                                response.code, null, params.mDataLoaderType, params.getUser());
+                        VerificationUtils.broadcastPackageVerified(verificationId, originUri,
+                                response.code, null, params.mDataLoaderType, params.getUser(),
+                                mPm.mContext);
                     } else {
                         params.setReturnCode(
                                 PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
@@ -493,290 +396,6 @@
         }
     }
 
-    private void handlePackagePostInstall(PackageInstalledInfo res, boolean killApp,
-            boolean virtualPreload, boolean launchedForRestore, String installerPackage,
-            IPackageInstallObserver2 installObserver, int dataLoaderType) {
-        boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED;
-        final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null;
-        final String packageName = res.mName;
-        final PackageStateInternal pkgSetting =
-                succeeded ? mPm.getPackageStateInternal(packageName) : null;
-        final boolean removedBeforeUpdate = (pkgSetting == null)
-                || (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals(
-                res.mPkg.getPath()));
-        if (succeeded && removedBeforeUpdate) {
-            Slog.e(TAG, packageName + " was removed before handlePackagePostInstall "
-                    + "could be executed");
-            res.mReturnCode = INSTALL_FAILED_PACKAGE_CHANGED;
-            res.mReturnMsg = "Package was removed before install could complete.";
-
-            // Remove the update failed package's older resources safely now
-            InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null;
-            if (args != null) {
-                synchronized (mPm.mInstallLock) {
-                    args.doPostDeleteLI(true);
-                }
-            }
-            mPm.notifyInstallObserver(res, installObserver);
-            return;
-        }
-
-        if (succeeded) {
-            // Clear the uid cache after we installed a new package.
-            mPm.mPerUidReadTimeoutsCache = null;
-
-            // Send the removed broadcasts
-            if (res.mRemovedInfo != null) {
-                res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
-            }
-
-            final String installerPackageName =
-                    res.mInstallerPackageName != null
-                            ? res.mInstallerPackageName
-                            : res.mRemovedInfo != null
-                                    ? res.mRemovedInfo.mInstallerPackageName
-                                    : null;
-
-            synchronized (mPm.mLock) {
-                mPm.mInstantAppRegistry.onPackageInstalledLPw(res.mPkg, res.mNewUsers);
-            }
-
-            // Determine the set of users who are adding this package for
-            // the first time vs. those who are seeing an update.
-            int[] firstUserIds = EMPTY_INT_ARRAY;
-            int[] firstInstantUserIds = EMPTY_INT_ARRAY;
-            int[] updateUserIds = EMPTY_INT_ARRAY;
-            int[] instantUserIds = EMPTY_INT_ARRAY;
-            final boolean allNewUsers = res.mOrigUsers == null || res.mOrigUsers.length == 0;
-            for (int newUser : res.mNewUsers) {
-                final boolean isInstantApp = pkgSetting.getUserStateOrDefault(newUser)
-                        .isInstantApp();
-                if (allNewUsers) {
-                    if (isInstantApp) {
-                        firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);
-                    } else {
-                        firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);
-                    }
-                    continue;
-                }
-                boolean isNew = true;
-                for (int origUser : res.mOrigUsers) {
-                    if (origUser == newUser) {
-                        isNew = false;
-                        break;
-                    }
-                }
-                if (isNew) {
-                    if (isInstantApp) {
-                        firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);
-                    } else {
-                        firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);
-                    }
-                } else {
-                    if (isInstantApp) {
-                        instantUserIds = ArrayUtils.appendInt(instantUserIds, newUser);
-                    } else {
-                        updateUserIds = ArrayUtils.appendInt(updateUserIds, newUser);
-                    }
-                }
-            }
-
-            // Send installed broadcasts if the package is not a static shared lib.
-            if (res.mPkg.getStaticSharedLibName() == null) {
-                mPm.mProcessLoggingHandler.invalidateBaseApkHash(res.mPkg.getBaseApkPath());
-
-                // Send added for users that see the package for the first time
-                // sendPackageAddedForNewUsers also deals with system apps
-                int appId = UserHandle.getAppId(res.mUid);
-                boolean isSystem = res.mPkg.isSystem();
-                mPm.sendPackageAddedForNewUsers(packageName, isSystem || virtualPreload,
-                        virtualPreload /*startReceiver*/, appId, firstUserIds, firstInstantUserIds,
-                        dataLoaderType);
-
-                // Send added for users that don't see the package for the first time
-                Bundle extras = new Bundle(1);
-                extras.putInt(Intent.EXTRA_UID, res.mUid);
-                if (update) {
-                    extras.putBoolean(Intent.EXTRA_REPLACING, true);
-                }
-                extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
-                // Send to all running apps.
-                final SparseArray<int[]> newBroadcastAllowList;
-
-                synchronized (mPm.mLock) {
-                    newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
-                            mPm.getPackageStateInternal(res.mName, Process.SYSTEM_UID),
-                            updateUserIds, mPm.mSettings.getPackagesLocked());
-                }
-                mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, 0 /*flags*/,
-                        null /*targetPackage*/, null /*finishedReceiver*/,
-                        updateUserIds, instantUserIds, newBroadcastAllowList, null);
-                if (installerPackageName != null) {
-                    // Send to the installer, even if it's not running.
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                            extras, 0 /*flags*/,
-                            installerPackageName, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
-                }
-                // if the required verifier is defined, but, is not the installer of record
-                // for the package, it gets notified
-                final boolean notifyVerifier = mPm.mRequiredVerifierPackage != null
-                        && !mPm.mRequiredVerifierPackage.equals(installerPackageName);
-                if (notifyVerifier) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                            extras, 0 /*flags*/,
-                            mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastAllowList */, null);
-                }
-                // If package installer is defined, notify package installer about new
-                // app installed
-                if (mPm.mRequiredInstallerPackage != null) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                            extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
-                            mPm.mRequiredInstallerPackage, null /*finishedReceiver*/,
-                            firstUserIds, instantUserIds, null /* broadcastAllowList */, null);
-                }
-
-                // Send replaced for users that don't see the package for the first time
-                if (update) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                            packageName, extras, 0 /*flags*/,
-                            null /*targetPackage*/, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList,
-                            null);
-                    if (installerPackageName != null) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                                extras, 0 /*flags*/,
-                                installerPackageName, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
-                    }
-                    if (notifyVerifier) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                                extras, 0 /*flags*/,
-                                mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
-                    }
-                    mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
-                            null /*package*/, null /*extras*/, 0 /*flags*/,
-                            packageName /*targetPackage*/,
-                            null /*finishedReceiver*/, updateUserIds, instantUserIds,
-                            null /*broadcastAllowList*/,
-                            mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions(
-                                    REASON_PACKAGE_REPLACED).toBundle());
-                } else if (launchedForRestore && !res.mPkg.isSystem()) {
-                    // First-install and we did a restore, so we're responsible for the
-                    // first-launch broadcast.
-                    if (DEBUG_BACKUP) {
-                        Slog.i(TAG, "Post-restore of " + packageName
-                                + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));
-                    }
-                    mBroadcastHelper.sendFirstLaunchBroadcast(packageName, installerPackage,
-                            firstUserIds, firstInstantUserIds);
-                }
-
-                // Send broadcast package appeared if external for all users
-                if (res.mPkg.isExternalStorage()) {
-                    if (!update) {
-                        final StorageManager storage = mPm.mInjector.getSystemService(
-                                StorageManager.class);
-                        VolumeInfo volume =
-                                storage.findVolumeByUuid(
-                                        StorageManager.convert(
-                                                res.mPkg.getVolumeUuid()).toString());
-                        int packageExternalStorageType =
-                                PackageManagerServiceUtils.getPackageExternalStorageType(volume,
-                                        res.mPkg.isExternalStorage());
-                        // If the package was installed externally, log it.
-                        if (packageExternalStorageType != StorageEnums.UNKNOWN) {
-                            FrameworkStatsLog.write(
-                                    FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED,
-                                    packageExternalStorageType, packageName);
-                        }
-                    }
-                    if (DEBUG_INSTALL) {
-                        Slog.i(TAG, "upgrading pkg " + res.mPkg + " is external");
-                    }
-                    final int[] uidArray = new int[]{res.mPkg.getUid()};
-                    ArrayList<String> pkgList = new ArrayList<>(1);
-                    pkgList.add(packageName);
-                    mBroadcastHelper.sendResourcesChangedBroadcast(
-                            true, true, pkgList, uidArray, null);
-                }
-            } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
-                for (int i = 0; i < res.mLibraryConsumers.size(); i++) {
-                    AndroidPackage pkg = res.mLibraryConsumers.get(i);
-                    // send broadcast that all consumers of the static shared library have changed
-                    mPm.sendPackageChangedBroadcast(pkg.getPackageName(), false /* dontKillApp */,
-                            new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
-                            pkg.getUid(), null);
-                }
-            }
-
-            // Work that needs to happen on first install within each user
-            if (firstUserIds != null && firstUserIds.length > 0) {
-                for (int userId : firstUserIds) {
-                    mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
-                            userId);
-                }
-            }
-
-            if (allNewUsers && !update) {
-                mPm.notifyPackageAdded(packageName, res.mUid);
-            } else {
-                mPm.notifyPackageChanged(packageName, res.mUid);
-            }
-
-            // Log current value of "unknown sources" setting
-            EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
-                    getUnknownSourcesSettings());
-
-            // Remove the replaced package's older resources safely now
-            InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null;
-            if (args != null) {
-                if (!killApp) {
-                    // If we didn't kill the app, defer the deletion of code/resource files, since
-                    // they may still be in use by the running application. This mitigates problems
-                    // in cases where resources or code is loaded by a new Activity before
-                    // ApplicationInfo changes have propagated to all application threads.
-                    scheduleDeferredNoKillPostDelete(args);
-                } else {
-                    synchronized (mPm.mInstallLock) {
-                        args.doPostDeleteLI(true);
-                    }
-                }
-            } else {
-                // Force a gc to clear up things. Ask for a background one, it's fine to go on
-                // and not block here.
-                VMRuntime.getRuntime().requestConcurrentGC();
-            }
-
-            // Notify DexManager that the package was installed for new users.
-            // The updated users should already be indexed and the package code paths
-            // should not change.
-            // Don't notify the manager for ephemeral apps as they are not expected to
-            // survive long enough to benefit of background optimizations.
-            for (int userId : firstUserIds) {
-                PackageInfo info = mPm.getPackageInfo(packageName, /*flags*/ 0, userId);
-                // There's a race currently where some install events may interleave with an
-                // uninstall. This can lead to package info being null (b/36642664).
-                if (info != null) {
-                    mPm.getDexManager().notifyPackageInstalled(info, userId);
-                }
-            }
-        }
-
-        final boolean deferInstallObserver = succeeded && update && !killApp;
-        if (deferInstallObserver) {
-            mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
-        } else {
-            mPm.notifyInstallObserver(res, installObserver);
-        }
-
-        // Prune unused static shared libraries which have been cached a period of time
-        mPm.schedulePruneUnusedStaticSharedLibraries(true /* delay */);
-    }
-
     /**
      * Get the default verification agent response code.
      *
@@ -800,20 +419,4 @@
         // an easy way to get around the integrity check.
         return PackageManager.VERIFICATION_REJECT;
     }
-
-    /**
-     * Get the "allow unknown sources" setting.
-     *
-     * @return the current "allow unknown sources" setting
-     */
-    private int getUnknownSourcesSettings() {
-        return android.provider.Settings.Secure.getIntForUser(mPm.mContext.getContentResolver(),
-                android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS,
-                -1, UserHandle.USER_SYSTEM);
-    }
-
-    private void scheduleDeferredNoKillPostDelete(InstallArgs args) {
-        Message message = obtainMessage(DEFERRED_NO_KILL_POST_DELETE, args);
-        sendMessageDelayed(message, DEFERRED_NO_KILL_POST_DELETE_DELAY_MS);
-    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8b26419..e71ac1a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1273,6 +1273,11 @@
         mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
     }
 
+    void scheduleDeferredNoKillPostDelete(InstallArgs args) {
+        Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_POST_DELETE, args);
+        mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_POST_DELETE_DELAY_MS);
+    }
+
     void schedulePruneUnusedStaticSharedLibraries(boolean delay) {
         mHandler.removeMessages(PRUNE_UNUSED_STATIC_SHARED_LIBRARIES);
         mHandler.sendEmptyMessageDelayed(PRUNE_UNUSED_STATIC_SHARED_LIBRARIES,
@@ -1409,6 +1414,32 @@
         }
     }
 
+    void writePendingRestrictions() {
+        synchronized (mLock) {
+            mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+            for (int userId : mDirtyUsers) {
+                mSettings.writePackageRestrictionsLPr(userId);
+            }
+            mDirtyUsers.clear();
+        }
+    }
+
+    void writeSettings() {
+        synchronized (mLock) {
+            mHandler.removeMessages(WRITE_SETTINGS);
+            mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+            writeSettingsLPrTEMP();
+            mDirtyUsers.clear();
+        }
+    }
+
+    void writePackageList(int userId) {
+        synchronized (mLock) {
+            mHandler.removeMessages(WRITE_PACKAGE_LIST);
+            mSettings.writePackageListLPr(userId);
+        }
+    }
+
     public static PackageManagerService main(Context context, Installer installer,
             @NonNull DomainVerificationService domainVerificationService, boolean factoryTest,
             boolean onlyCore) {
@@ -1801,7 +1832,7 @@
         mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this, mRemovePackageHelper,
                 mAppDataHelper);
         mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
-                mInitAndSystemPackageHelper, mAppDataHelper);
+                mAppDataHelper);
         mPreferredActivityHelper = new PreferredActivityHelper(this);
         mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper);
         mDexOptHelper = new DexOptHelper(this);
@@ -7845,7 +7876,8 @@
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                     || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
-                if (!mInitAndSystemPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting)) {
+                if (!new InstallPackageHelper(this).enableCompressedPackage(deletedPkg,
+                        pkgSetting)) {
                     Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable "
                             + "commpressed package " + setting.getPackageName());
                     updateAllowed[i] = false;
@@ -11134,11 +11166,24 @@
         return mIsPreNMR1Upgrade;
     }
 
-    InitAndSystemPackageHelper getInitAndSystemPackageHelper() {
-        return mInitAndSystemPackageHelper;
-    }
-
     boolean isOverlayMutable(String packageName) {
         return mOverlayConfig.isMutable(packageName);
     }
+
+    @ScanFlags int getSystemPackageScanFlags(File codePath) {
+        List<ScanPartition> dirsToScanAsSystem =
+                mInitAndSystemPackageHelper.getDirsToScanAsSystem();
+        @PackageManagerService.ScanFlags int scanFlags = SCAN_AS_SYSTEM;
+        for (int i = dirsToScanAsSystem.size() - 1; i >= 0; i--) {
+            ScanPartition partition = dirsToScanAsSystem.get(i);
+            if (partition.containsFile(codePath)) {
+                scanFlags |= partition.scanFlag;
+                if (partition.containsPrivApp(codePath)) {
+                    scanFlags |= SCAN_AS_PRIVILEGED;
+                }
+                break;
+            }
+        }
+        return scanFlags;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 9c41ccb..098c42e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -644,6 +644,35 @@
         return compatMatch;
     }
 
+    /**
+     * Decompress files stored in codePath to dstCodePath for a certain package.
+     */
+    public static int decompressFiles(String codePath, File dstCodePath, String packageName) {
+        final File[] compressedFiles = getCompressedFiles(codePath);
+        int ret = PackageManager.INSTALL_SUCCEEDED;
+        try {
+            makeDirRecursive(dstCodePath, 0755);
+            for (File srcFile : compressedFiles) {
+                final String srcFileName = srcFile.getName();
+                final String dstFileName = srcFileName.substring(
+                        0, srcFileName.length() - COMPRESSED_EXTENSION.length());
+                final File dstFile = new File(dstCodePath, dstFileName);
+                ret = decompressFile(srcFile, dstFile);
+                if (ret != PackageManager.INSTALL_SUCCEEDED) {
+                    logCriticalInfo(Log.ERROR, "Failed to decompress"
+                            + "; pkg: " + packageName
+                            + ", file: " + dstFileName);
+                    break;
+                }
+            }
+        } catch (ErrnoException e) {
+            logCriticalInfo(Log.ERROR, "Failed to decompress"
+                    + "; pkg: " + packageName
+                    + ", err: " + e.errno);
+        }
+        return ret;
+    }
+
     public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
         if (DEBUG_COMPRESSION) {
             Slog.i(TAG, "Decompress file"
diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java
index 7dec756..4dbc5d5 100644
--- a/services/core/java/com/android/server/pm/VerificationParams.java
+++ b/services/core/java/com/android/server/pm/VerificationParams.java
@@ -51,7 +51,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
-import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
 import android.content.pm.VerifierInfo;
 import android.content.pm.parsing.PackageLite;
@@ -62,7 +61,6 @@
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.ArraySet;
@@ -70,13 +68,9 @@
 import android.util.Slog;
 
 import com.android.server.DeviceIdleInternal;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.File;
-import java.security.PublicKey;
-import java.security.cert.CertificateException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
@@ -91,10 +85,6 @@
      */
     private static final long DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT = 30 * 1000;
     /**
-     * Whether verification is enabled by default.
-     */
-    private static final boolean DEFAULT_VERIFY_ENABLE = true;
-    /**
      * Timeout duration in milliseconds for enabling package rollback. If we fail to enable
      * rollback within that period, the install will proceed without rollback enabled.
      *
@@ -166,7 +156,7 @@
         PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
                 mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, mPackageAbiOverride);
 
-        Pair<Integer, String> ret = verifyReplacingVersionCode(
+        Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode(
                 pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
         setReturnCode(ret.first, ret.second);
         if (mRet != INSTALL_SUCCEEDED) {
@@ -186,7 +176,7 @@
         }
     }
 
-    void sendApkVerificationRequest(PackageInfoLite pkgLite) {
+    private void sendApkVerificationRequest(PackageInfoLite pkgLite) {
         final int verificationId = mPm.mPendingVerificationToken++;
 
         PackageVerificationState verificationState =
@@ -332,7 +322,7 @@
     /**
      * Send a request to verifier(s) to verify the package if necessary.
      */
-    void sendPackageVerificationRequest(
+    private void sendPackageVerificationRequest(
             int verificationId,
             PackageInfoLite pkgLite,
             PackageVerificationState verificationState) {
@@ -354,7 +344,7 @@
         verificationState.setRequiredVerifierUid(requiredUid);
         final int installerUid =
                 mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid;
-        final boolean isVerificationEnabled = isVerificationEnabled(
+        final boolean isVerificationEnabled = mInstallPackageHelper.isVerificationEnabled(
                 pkgLite, verifierUser.getIdentifier(), mInstallFlags, installerUid);
         final boolean isV4Signed =
                 (mSigningDetails.getSignatureSchemeVersion() == SIGNING_BLOCK_V4);
@@ -416,7 +406,7 @@
 
             DeviceIdleInternal idleController =
                     mPm.mInjector.getLocalService(DeviceIdleInternal.class);
-            final long idleDuration = mVerificationHelper.getVerificationTimeout();
+            final long idleDuration = VerificationUtils.getVerificationTimeout(mPm.mContext);
             final BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setTemporaryAppAllowlist(idleDuration,
                     TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
@@ -473,7 +463,7 @@
                                         .obtainMessage(CHECK_PENDING_VERIFICATION);
                                 msg.arg1 = verificationId;
                                 mPm.mHandler.sendMessageDelayed(msg,
-                                        mVerificationHelper.getVerificationTimeout());
+                                        VerificationUtils.getVerificationTimeout(mPm.mContext));
                             }
                         }, null, 0, null, null);
 
@@ -510,7 +500,7 @@
                 continue;
             }
 
-            final int verifierUid = getUidForVerifier(verifierInfo);
+            final int verifierUid = mInstallPackageHelper.getUidForVerifier(verifierInfo);
             if (verifierUid == -1) {
                 continue;
             }
@@ -526,44 +516,6 @@
         return sufficientVerifiers;
     }
 
-    private int getUidForVerifier(VerifierInfo verifierInfo) {
-        synchronized (mPm.mLock) {
-            final AndroidPackage pkg = mPm.mPackages.get(verifierInfo.packageName);
-            if (pkg == null) {
-                return -1;
-            } else if (pkg.getSigningDetails().getSignatures().length != 1) {
-                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
-                        + " has more than one signature; ignoring");
-                return -1;
-            }
-
-            /*
-             * If the public key of the package's signature does not match
-             * our expected public key, then this is a different package and
-             * we should skip.
-             */
-
-            final byte[] expectedPublicKey;
-            try {
-                final Signature verifierSig = pkg.getSigningDetails().getSignatures()[0];
-                final PublicKey publicKey = verifierSig.getPublicKey();
-                expectedPublicKey = publicKey.getEncoded();
-            } catch (CertificateException e) {
-                return -1;
-            }
-
-            final byte[] actualPublicKey = verifierInfo.publicKey.getEncoded();
-
-            if (!Arrays.equals(actualPublicKey, expectedPublicKey)) {
-                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
-                        + " does not have the expected public key; ignoring");
-                return -1;
-            }
-
-            return pkg.getUid();
-        }
-    }
-
     private static ComponentName matchComponentForVerifier(String packageName,
             List<ResolveInfo> receivers) {
         ActivityInfo targetReceiver = null;
@@ -588,56 +540,6 @@
         return new ComponentName(targetReceiver.packageName, targetReceiver.name);
     }
 
-    /**
-     * Check whether or not package verification has been enabled.
-     *
-     * @return true if verification should be performed
-     */
-    private boolean isVerificationEnabled(
-            PackageInfoLite pkgInfoLite, int userId, int installFlags, int installerUid) {
-        if (!DEFAULT_VERIFY_ENABLE) {
-            return false;
-        }
-
-        // Check if installing from ADB
-        if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
-            if (mPm.isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) {
-                return true;
-            }
-            // Check if the developer wants to skip verification for ADB installs
-            if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) {
-                synchronized (mPm.mLock) {
-                    if (mPm.mSettings.getPackageLPr(pkgInfoLite.packageName) == null) {
-                        // Always verify fresh install
-                        return true;
-                    }
-                }
-                // Only skip when apk is debuggable
-                return !pkgInfoLite.debuggable;
-            }
-            return Settings.Global.getInt(mPm.mContext.getContentResolver(),
-                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
-        }
-
-        // only when not installed from ADB, skip verification for instant apps when
-        // the installer and verifier are the same.
-        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
-            if (mPm.mInstantAppInstallerActivity != null
-                    && mPm.mInstantAppInstallerActivity.packageName.equals(
-                    mPm.mRequiredVerifierPackage)) {
-                try {
-                    mPm.mInjector.getSystemService(AppOpsManager.class)
-                            .checkPackage(installerUid, mPm.mRequiredVerifierPackage);
-                    if (DEBUG_VERIFY) {
-                        Slog.i(TAG, "disable verification for instant app");
-                    }
-                    return false;
-                } catch (SecurityException ignore) { }
-            }
-        }
-        return true;
-    }
-
     void populateInstallerExtras(Intent intent) {
         intent.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
                 mInstallSource.initiatingPackageName);
diff --git a/services/core/java/com/android/server/pm/VerificationHelper.java b/services/core/java/com/android/server/pm/VerificationUtils.java
similarity index 84%
rename from services/core/java/com/android/server/pm/VerificationHelper.java
rename to services/core/java/com/android/server/pm/VerificationUtils.java
index b04d6de..4392b47 100644
--- a/services/core/java/com/android/server/pm/VerificationHelper.java
+++ b/services/core/java/com/android/server/pm/VerificationUtils.java
@@ -27,36 +27,30 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 
-public final class VerificationHelper {
+final class VerificationUtils {
     /**
      * The default maximum time to wait for the verification agent to return in
      * milliseconds.
      */
     private static final long DEFAULT_VERIFICATION_TIMEOUT = 10 * 1000;
 
-    Context mContext;
-
-    public VerificationHelper(Context context) {
-        mContext = context;
-    }
-
     /**
      * Get the verification agent timeout.  Used for both the APK verifier and the
      * intent filter verifier.
      *
      * @return verification timeout in milliseconds
      */
-    public long getVerificationTimeout() {
-        long timeout = Settings.Global.getLong(mContext.getContentResolver(),
+    public static long getVerificationTimeout(Context context) {
+        long timeout = Settings.Global.getLong(context.getContentResolver(),
                 Settings.Global.PACKAGE_VERIFIER_TIMEOUT, DEFAULT_VERIFICATION_TIMEOUT);
         // The setting can be used to increase the timeout but not decrease it, since that is
         // equivalent to disabling the verifier.
         return Math.max(timeout, DEFAULT_VERIFICATION_TIMEOUT);
     }
 
-    public void broadcastPackageVerified(int verificationId, Uri packageUri,
+    public static void broadcastPackageVerified(int verificationId, Uri packageUri,
             int verificationCode, @Nullable String rootHashString, int dataLoaderType,
-            UserHandle user) {
+            UserHandle user, Context context) {
         final Intent intent = new Intent(Intent.ACTION_PACKAGE_VERIFIED);
         intent.setDataAndType(packageUri, PACKAGE_MIME_TYPE);
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -67,7 +61,7 @@
         }
         intent.putExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
 
-        mContext.sendBroadcastAsUser(intent, user,
+        context.sendBroadcastAsUser(intent, user,
                 android.Manifest.permission.PACKAGE_VERIFICATION_AGENT);
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 962816c..0fb8475 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -899,7 +899,7 @@
                     oldPkgState.getUserStates();
             int oldUserStatesSize = oldUserStates.size();
             if (oldUserStatesSize > 0) {
-                ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg);
+                ArraySet<String> newWebDomains = mCollector.collectAllWebDomains(newPkg);
                 for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
                         oldUserStatesIndex++) {
                     int userId = oldUserStates.keyAt(oldUserStatesIndex);
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 7826ddf..ab61ef4 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -95,7 +95,6 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.health.V2_0.IHealth;
 import android.net.ConnectivityManager;
 import android.net.INetworkStatsService;
 import android.net.INetworkStatsSession;
@@ -194,13 +193,13 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.role.RoleManagerLocal;
-import com.android.server.BatteryService;
 import com.android.server.BinderCallsStatsService;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.health.HealthServiceWrapper;
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
 import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
@@ -230,6 +229,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.MissingResourceException;
+import java.util.NoSuchElementException;
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
@@ -358,7 +358,7 @@
     private File mBaseDir;
 
     @GuardedBy("mHealthHalLock")
-    private BatteryService.HealthServiceWrapper mHealthService;
+    private HealthServiceWrapper mHealthService;
 
     @Nullable
     @GuardedBy("mCpuTimePerThreadFreqLock")
@@ -807,10 +807,9 @@
                 KernelCpuThreadReaderSettingsObserver.getSettingsModifiedReader(mContext);
 
         // Initialize HealthService
-        mHealthService = new BatteryService.HealthServiceWrapper();
         try {
-            mHealthService.init();
-        } catch (RemoteException e) {
+            mHealthService = HealthServiceWrapper.create(null);
+        } catch (RemoteException | NoSuchElementException e) {
             Slog.e(TAG, "failed to initialize healthHalWrapper");
         }
 
@@ -3990,38 +3989,40 @@
     }
 
     int pullHealthHalLocked(int atomTag, List<StatsEvent> pulledData) {
-        IHealth healthService = mHealthService.getLastService();
-        if (healthService == null) {
+        if (mHealthService == null) {
             return StatsManager.PULL_SKIP;
         }
+        android.hardware.health.V1_0.HealthInfo healthInfo;
         try {
-            healthService.getHealthInfo((result, value) -> {
-                int pulledValue;
-                switch(atomTag) {
-                    case FrameworkStatsLog.BATTERY_LEVEL:
-                        pulledValue = value.legacy.batteryLevel;
-                        break;
-                    case FrameworkStatsLog.REMAINING_BATTERY_CAPACITY:
-                        pulledValue = value.legacy.batteryChargeCounter;
-                        break;
-                    case FrameworkStatsLog.FULL_BATTERY_CAPACITY:
-                        pulledValue = value.legacy.batteryFullCharge;
-                        break;
-                    case FrameworkStatsLog.BATTERY_VOLTAGE:
-                        pulledValue = value.legacy.batteryVoltage;
-                        break;
-                    case FrameworkStatsLog.BATTERY_CYCLE_COUNT:
-                        pulledValue = value.legacy.batteryCycleCount;
-                        break;
-                    default:
-                        throw new IllegalStateException("Invalid atomTag in healthHal puller: "
-                                + atomTag);
-                }
-                pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pulledValue));
-            });
+            healthInfo = mHealthService.getHealthInfo();
         } catch (RemoteException | IllegalStateException e) {
             return StatsManager.PULL_SKIP;
         }
+        if (healthInfo == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        int pulledValue;
+        switch (atomTag) {
+            case FrameworkStatsLog.BATTERY_LEVEL:
+                pulledValue = healthInfo.batteryLevel;
+                break;
+            case FrameworkStatsLog.REMAINING_BATTERY_CAPACITY:
+                pulledValue = healthInfo.batteryChargeCounter;
+                break;
+            case FrameworkStatsLog.FULL_BATTERY_CAPACITY:
+                pulledValue = healthInfo.batteryFullCharge;
+                break;
+            case FrameworkStatsLog.BATTERY_VOLTAGE:
+                pulledValue = healthInfo.batteryVoltage;
+                break;
+            case FrameworkStatsLog.BATTERY_CYCLE_COUNT:
+                pulledValue = healthInfo.batteryCycleCount;
+                break;
+            default:
+                return StatsManager.PULL_SKIP;
+        }
+        pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pulledValue));
         return StatsManager.PULL_SUCCESS;
     }
 
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 239a916..1c46ac8 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -98,6 +98,7 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -2048,7 +2049,8 @@
         return builder.build();
     }
 
-    private static LinkProperties buildConnectedLinkProperties(
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    LinkProperties buildConnectedLinkProperties(
             @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
             @NonNull IpSecTunnelInterface tunnelIface,
             @NonNull VcnChildSessionConfiguration childConfig,
@@ -2076,6 +2078,13 @@
 
             lp.setTcpBufferSizes(underlyingLp.getTcpBufferSizes());
             underlyingMtu = underlyingLp.getMtu();
+
+            // WiFi LinkProperties uses DHCP as the sole source of MTU information, and as a result
+            // often lists MTU as 0 (see b/184678973). Use the interface MTU as retrieved by
+            // NetworkInterface APIs.
+            if (underlyingMtu == 0 && underlyingLp.getInterfaceName() != null) {
+                underlyingMtu = mDeps.getUnderlyingIfaceMtu(underlyingLp.getInterfaceName());
+            }
         } else {
             Slog.wtf(
                     TAG,
@@ -2417,6 +2426,17 @@
         public long getElapsedRealTime() {
             return SystemClock.elapsedRealtime();
         }
+
+        /** Gets the MTU for the given underlying interface. */
+        public int getUnderlyingIfaceMtu(String ifaceName) {
+            try {
+                final NetworkInterface underlyingIface = NetworkInterface.getByName(ifaceName);
+                return underlyingIface == null ? 0 : underlyingIface.getMTU();
+            } catch (IOException e) {
+                Slog.d(TAG, "Could not get MTU of underlying network", e);
+                return 0;
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3d83995..ec0b5f0 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -20,11 +20,11 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK;
 import static android.os.Build.IS_USER;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
 import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
@@ -926,6 +926,8 @@
                     final int windowType = windowState.mAttrs.type;
                     if (isExcludedWindowType(windowType)
                             || ((windowState.mAttrs.privateFlags
+                            & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0)
+                            || ((windowState.mAttrs.privateFlags
                             & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
                         continue;
                     }
@@ -990,7 +992,6 @@
                         }
                     }
                 }
-
                 visibleWindows.clear();
 
                 mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
@@ -1027,9 +1028,6 @@
 
             private boolean isExcludedWindowType(int windowType) {
                 return windowType == TYPE_MAGNIFICATION_OVERLAY
-                        // Omit the touch region to avoid the cut out of the magnification
-                        // bounds because nav bar panel is unmagnifiable.
-                        || windowType == TYPE_NAVIGATION_BAR_PANEL
                         // Omit the touch region of window magnification to avoid the cut out of the
                         // magnification and the magnified center of window magnification could be
                         // in the bounds
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 241e7e2..7a38554 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5981,7 +5981,8 @@
                 // Reparent the SurfaceControl of this DisplayContent to null, to prevent content
                 // being added to it. This ensures that no app launched explicitly on the
                 // VirtualDisplay will show up as part of the mirrored content.
-                .reparent(mWindowingLayer, null);
+                .reparent(mWindowingLayer, null)
+                .reparent(mOverlayLayer, null);
         // Retrieve the size of the DisplayArea to mirror.
         updateMirroredSurface(transaction, wc.getDisplayContent().getBounds(), surfaceSize);
         mTokenToMirror = tokenToMirror;
@@ -6011,7 +6012,9 @@
                     // Reparent the SurfaceControl of this DisplayContent back to mSurfaceControl,
                     // to allow content to be added to it. This allows this DisplayContent to stop
                     // mirroring and show content normally.
-                    .reparent(mWindowingLayer, mSurfaceControl).apply();
+                    .reparent(mWindowingLayer, mSurfaceControl)
+                    .reparent(mOverlayLayer, mSurfaceControl)
+                    .apply();
             // Stop mirroring by destroying the reference to the mirrored layer.
             mMirroredSurface = null;
             // Do not un-set the token, in case content is removed and mirroring should begin again.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 4d986a2..3387e37 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -48,6 +48,7 @@
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.Color;
 import android.os.UserHandle;
 import android.util.IntArray;
 import android.util.Slog;
@@ -81,9 +82,9 @@
     DisplayContent mDisplayContent;
 
     /**
-     * A color layer that serves as a solid color background to certain animations.
+     * Keeps track of the last set color layer so that it can be reset during surface migrations.
      */
-    private SurfaceControl mColorBackgroundLayer;
+    private @ColorInt int mBackgroundColor = 0;
 
     /**
      * This counter is used to make sure we don't prematurely clear the background color in the
@@ -357,6 +358,14 @@
     }
 
     @Override
+    void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
+        // We want an effect layer instead of the default container layer so that we can set a
+        // background color on it for task animations.
+        b.setEffectLayer();
+        super.setInitialSurfaceControlProperties(b);
+    }
+
+    @Override
     void addChild(WindowContainer child, int position) {
         if (child.asTaskDisplayArea() != null) {
             if (DEBUG_ROOT_TASK) {
@@ -875,11 +884,6 @@
     void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
         if (getParent() != null) {
             super.onParentChanged(newParent, oldParent, () -> {
-                mColorBackgroundLayer = makeChildSurface(null)
-                        .setColorLayer()
-                        .setName("colorBackgroundLayer")
-                        .setCallsite("TaskDisplayArea.onParentChanged")
-                        .build();
                 mSplitScreenDividerAnchor = makeChildSurface(null)
                         .setName("splitScreenDividerAnchor")
                         .setCallsite("TaskDisplayArea.onParentChanged")
@@ -891,28 +895,18 @@
         } else {
             super.onParentChanged(newParent, oldParent);
             mWmService.mTransactionFactory.get()
-                    .remove(mColorBackgroundLayer)
                     .remove(mSplitScreenDividerAnchor)
                     .apply();
-            mColorBackgroundLayer = null;
             mSplitScreenDividerAnchor = null;
         }
     }
 
-    void setBackgroundColor(@ColorInt int color) {
-        if (mColorBackgroundLayer == null) {
-            return;
-        }
-
-        float r = ((color >> 16) & 0xff) / 255.0f;
-        float g = ((color >>  8) & 0xff) / 255.0f;
-        float b = ((color >>  0) & 0xff) / 255.0f;
-
+    void setBackgroundColor(@ColorInt int colorInt) {
+        mBackgroundColor = colorInt;
+        Color color = Color.valueOf(colorInt);
         mColorLayerCounter++;
-
         getPendingTransaction()
-                .setColor(mColorBackgroundLayer, new float[]{r, g, b})
-                .show(mColorBackgroundLayer);
+                .setColor(mSurfaceControl, new float[]{color.red(), color.green(), color.blue()});
 
         scheduleAnimation();
     }
@@ -923,7 +917,7 @@
         // Only clear the color layer if we have received the same amounts of clear as set
         // requests.
         if (mColorLayerCounter == 0) {
-            getPendingTransaction().hide(mColorBackgroundLayer);
+            getPendingTransaction().unsetColor(mSurfaceControl);
             scheduleAnimation();
         }
     }
@@ -931,12 +925,16 @@
     @Override
     void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
         super.migrateToNewSurfaceControl(t);
-        if (mColorBackgroundLayer == null) {
+
+        if (mColorLayerCounter > 0) {
+            setBackgroundColor(mBackgroundColor);
+        }
+
+        if (mSplitScreenDividerAnchor == null) {
             return;
         }
 
         // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
-        t.reparent(mColorBackgroundLayer, mSurfaceControl);
         t.reparent(mSplitScreenDividerAnchor, mSurfaceControl);
         reassignLayer(t);
         scheduleAnimation();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 49bbd8a..0eaa25b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -31,6 +31,7 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.os.Process.INVALID_UID;
 import static android.os.UserHandle.USER_NULL;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -220,6 +221,8 @@
     /** Organizer that organizing this TaskFragment. */
     @Nullable
     private ITaskFragmentOrganizer mTaskFragmentOrganizer;
+    private int mTaskFragmentOrganizerUid = INVALID_UID;
+    private @Nullable String mTaskFragmentOrganizerProcessName;
 
     /** Client assigned unique token for this TaskFragment if this is created by an organizer. */
     @Nullable
@@ -232,13 +235,6 @@
      */
     private boolean mDelayLastActivityRemoval;
 
-    /**
-     * The PID of the organizer that created this TaskFragment. It should be the same as the PID
-     * of {@link android.window.TaskFragmentCreationParams#getOwnerToken()}.
-     * {@link ActivityRecord#INVALID_PID} if this is not an organizer-created TaskFragment.
-     */
-    private int mTaskFragmentOrganizerPid = ActivityRecord.INVALID_PID;
-
     final Point mLastSurfaceSize = new Point();
 
     private final Rect mTmpInsets = new Rect();
@@ -334,9 +330,11 @@
         mDelayLastActivityRemoval = false;
     }
 
-    void setTaskFragmentOrganizer(TaskFragmentOrganizerToken organizer, int pid) {
+    void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
+            @NonNull String processName) {
         mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
-        mTaskFragmentOrganizerPid = pid;
+        mTaskFragmentOrganizerUid = uid;
+        mTaskFragmentOrganizerProcessName = processName;
     }
 
     /** Whether this TaskFragment is organized by the given {@code organizer}. */
@@ -2176,9 +2174,11 @@
         List<IBinder> childActivities = new ArrayList<>();
         for (int i = 0; i < getChildCount(); i++) {
             WindowContainer wc = getChildAt(i);
-            if (mTaskFragmentOrganizerPid != ActivityRecord.INVALID_PID
+            if (mTaskFragmentOrganizerUid != INVALID_UID
                     && wc.asActivityRecord() != null
-                    && wc.asActivityRecord().getPid() == mTaskFragmentOrganizerPid) {
+                    && wc.asActivityRecord().info.processName.equals(
+                            mTaskFragmentOrganizerProcessName)
+                    && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) {
                 // Only includes Activities that belong to the organizer process for security.
                 childActivities.add(wc.asActivityRecord().token);
             }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index f6c8356..8ecf685 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1204,8 +1204,8 @@
                 creationParams.getFragmentToken(), true /* createdByOrganizer */);
         // Set task fragment organizer immediately, since it might have to be notified about further
         // actions.
-        taskFragment.setTaskFragmentOrganizer(
-                creationParams.getOrganizer(), ownerActivity.getPid());
+        taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
+                ownerActivity.getUid(), ownerActivity.info.processName);
         ownerActivity.getTask().addChild(taskFragment, POSITION_TOP);
         taskFragment.setWindowingMode(creationParams.getWindowingMode());
         taskFragment.setBounds(creationParams.getInitialBounds());
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 3ffce8c..ad79c65 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -624,6 +624,60 @@
     }
 
     @Test
+    fun migratePackageSelected() {
+        val pkgName = PKG_ONE
+        val pkgBefore = mockPkgState(pkgName, UUID_ONE, SIGNATURE_ONE,
+            listOf(DOMAIN_1), listOf(DOMAIN_2))
+        val pkgAfter = mockPkgState(pkgName, UUID_TWO, SIGNATURE_TWO,
+            listOf(DOMAIN_1), listOf(DOMAIN_2))
+
+        val map = mutableMapOf<String, PackageStateInternal>()
+        val service = makeService { map[it] }
+        service.addPackage(pkgBefore)
+
+        // Only insert the package after addPackage call to ensure the service doesn't access
+        // a live package inside the addPackage logic. It should only use the provided input.
+        map[pkgName] = pkgBefore
+
+        assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
+            .isEqualTo(DomainVerificationManager.STATUS_OK)
+
+        assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
+            .isEqualTo(DomainVerificationManager.STATUS_OK)
+
+        service.getInfo(pkgName).run {
+            assertThat(identifier).isEqualTo(UUID_ONE)
+            assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to STATE_SUCCESS,
+            ))
+        }
+        assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+                DOMAIN_2 to DOMAIN_STATE_SELECTED,
+        ))
+        assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+
+        // Now remove the package because migrateState shouldn't use it either
+        map.remove(pkgName)
+
+        service.migrateState(pkgBefore, pkgAfter)
+
+        map[pkgName] = pkgAfter
+
+        service.getInfo(pkgName).run {
+            assertThat(identifier).isEqualTo(UUID_TWO)
+            assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to STATE_SUCCESS,
+            ))
+        }
+        assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+                DOMAIN_2 to DOMAIN_STATE_SELECTED,
+        ))
+        assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+    }
+
+    @Test
     fun backupAndRestore() {
         // This test acts as a proxy for true user restore through PackageManager,
         // as that's much harder to test for real.
@@ -804,7 +858,8 @@
         pkgName: String,
         domainSetId: UUID,
         signature: String,
-        domains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
+        autoVerifyDomains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
+        otherDomains: List<String> = listOf(),
         isSystemApp: Boolean = false
     ) = mockThrowOnUnmocked<PackageStateInternal> {
         val pkg = mockThrowOnUnmocked<AndroidPackage> {
@@ -812,23 +867,25 @@
             whenever(targetSdkVersion) { Build.VERSION_CODES.S }
             whenever(isEnabled) { true }
 
+            fun baseIntent(domain: String) = ParsedIntentInfoImpl().apply {
+                intentFilter.apply {
+                    addAction(Intent.ACTION_VIEW)
+                    addCategory(Intent.CATEGORY_BROWSABLE)
+                    addCategory(Intent.CATEGORY_DEFAULT)
+                    addDataScheme("http")
+                    addDataScheme("https")
+                    addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+                    addDataAuthority(domain, null)
+                }
+            }
+
             val activityList = listOf(
                 ParsedActivityImpl().apply {
-                    domains.forEach {
-                        addIntent(
-                            ParsedIntentInfoImpl().apply {
-                                intentFilter.apply {
-                                    autoVerify = true
-                                    addAction(Intent.ACTION_VIEW)
-                                    addCategory(Intent.CATEGORY_BROWSABLE)
-                                    addCategory(Intent.CATEGORY_DEFAULT)
-                                    addDataScheme("http")
-                                    addDataScheme("https")
-                                    addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
-                                    addDataAuthority(it, null)
-                                }
-                            }
-                        )
+                    autoVerifyDomains.forEach {
+                        addIntent(baseIntent(it).apply { intentFilter.autoVerify = true })
+                    }
+                    otherDomains.forEach {
+                        addIntent(baseIntent(it).apply { intentFilter.autoVerify = false })
                     }
                 },
             )
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java
index df19be4..a34db63 100644
--- a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.compat.overrides;
 
+import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -31,6 +33,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import libcore.util.HexEncoding;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -182,16 +186,18 @@
 
     @Test
     public void parsePackageOverrides_emptyConfigNoOwnedChangeIds_returnsEmpty() {
-        Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
-                /* configStr= */ "", /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "", PACKAGE_1, /* versionCode= */ 0, /* changeIdsToSkip= */
+                emptySet());
 
         assertThat(result).isEmpty();
     }
 
     @Test
     public void parsePackageOverrides_configWithSingleOverride_returnsOverride() {
-        Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
-                /* configStr= */ "123:::true", /* versionCode= */ 5, /* changeIdsToSkip= */
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "123:::true", PACKAGE_1, /* versionCode= */
+                5, /* changeIdsToSkip= */
                 emptySet());
 
         assertThat(result).hasSize(1);
@@ -201,10 +207,10 @@
 
     @Test
     public void parsePackageOverrides_configWithMultipleOverrides_returnsOverrides() {
-        Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
                 /* configStr= */ "910:3:4:false,78:10::false,12:::false,34:1:2:true,34:10::true,"
                         + "56::2:true,56:3:4:false,34:4:8:true,78:6:7:true,910:5::true,"
-                        + "1112::5:true,56:6::true,1112:6:7:false", /* versionCode= */
+                        + "1112::5:true,56:6::true,1112:6:7:false", PACKAGE_1, /* versionCode= */
                 5, /* changeIdsToSkip= */ emptySet());
 
         assertThat(result).hasSize(6);
@@ -228,8 +234,9 @@
     @Test
     public void parsePackageOverrides_changeIdsToSkipSpecified_returnsWithoutChangeIdsToSkip() {
         ArraySet<Long> changeIdsToSkip = new ArraySet<>(Arrays.asList(34L, 56L));
-        Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
-                /* configStr= */ "12:::true,56:3:7:true", /* versionCode= */ 5, changeIdsToSkip);
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "12:::true,56:3:7:true", PACKAGE_1, /* versionCode= */ 5,
+                changeIdsToSkip);
 
         assertThat(result).hasSize(1);
         assertThat(result.get(12L)).isEqualTo(
@@ -239,8 +246,77 @@
     @Test
     public void parsePackageOverrides_changeIdsToSkipContainsAllIds_returnsEmpty() {
         ArraySet<Long> changeIdsToSkip = new ArraySet<>(Arrays.asList(12L, 34L));
-        Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
-                /* configStr= */ "12:::true", /* versionCode= */ 5, changeIdsToSkip);
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "12:::true", PACKAGE_1, /* versionCode= */ 5, changeIdsToSkip);
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_signatureInvalid() {
+        when(mPackageManager.hasSigningCertificate(PACKAGE_1, HexEncoding.decode("aa"),
+                CERT_INPUT_SHA256)).thenReturn(false);
+
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "aa~12:::true,56:::true", PACKAGE_1,
+                /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_signatureInvalid_oddNumberOfCharacters() {
+        // Valid hex encoding should always be an even number of characters.
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "a~12:::true,56:::true", PACKAGE_1,
+                /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_signatureValid() {
+        when(mPackageManager.hasSigningCertificate(PACKAGE_1, HexEncoding.decode("bb"),
+                CERT_INPUT_SHA256)).thenReturn(true);
+
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "bb~12:::true,56:::false", PACKAGE_1,
+                /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result.keySet()).containsExactly(12L, 56L);
+    }
+
+    @Test
+    public void parsePackageOverrides_emptySignature() {
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "~12:::true,56:::false", PACKAGE_1,
+                /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result.keySet()).containsExactly(12L, 56L);
+    }
+
+    @Test
+    public void parsePackageOverrides_multipleSignatures() {
+        when(mPackageManager.hasSigningCertificate(PACKAGE_1, HexEncoding.decode("aa"),
+                CERT_INPUT_SHA256)).thenReturn(true);
+        when(mPackageManager.hasSigningCertificate(PACKAGE_1, HexEncoding.decode("bb"),
+                CERT_INPUT_SHA256)).thenReturn(true);
+
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "aa~bb~12:::true,56:::false", PACKAGE_1,
+                /* versionCode= */0, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_signatureOnly() {
+        when(mPackageManager.hasSigningCertificate(PACKAGE_1, HexEncoding.decode("aa"),
+                CERT_INPUT_SHA256)).thenReturn(true);
+
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
+                /* configStr= */ "aa~", PACKAGE_1,
+                /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
 
         assertThat(result).isEmpty();
     }
@@ -248,9 +324,9 @@
     @Test
     public void parsePackageOverrides_someOverridesAreInvalid_returnsWithoutInvalidOverrides() {
         // We add a valid entry before and after the invalid ones to make sure they are applied.
-        Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+        Map<Long, PackageOverride> result = mParser.parsePackageOverrides(
                 /* configStr= */ "12:::True,56:1:2:FALSE,56:3:true,78:4:8:true:,C1:::true,910:::no,"
-                        + "1112:1:ten:true,1112:one:10:true,,1314:7:3:false,34:::",
+                        + "1112:1:ten:true,1112:one:10:true,,1314:7:3:false,34:::", PACKAGE_1,
                 /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
 
         assertThat(result).hasSize(2);
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
deleted file mode 100644
index a2ecbc3..0000000
--- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static junit.framework.Assert.*;
-
-import static org.mockito.Mockito.*;
-
-import android.hardware.health.V2_0.IHealth;
-import android.hidl.manager.V1_0.IServiceManager;
-import android.hidl.manager.V1_0.IServiceNotification;
-import android.test.AndroidTestCase;
-
-import androidx.test.filters.SmallTest;
-
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.NoSuchElementException;
-
-public class BatteryServiceTest extends AndroidTestCase {
-
-    @Mock IServiceManager mMockedManager;
-    @Mock IHealth mMockedHal;
-    @Mock IHealth mMockedHal2;
-
-    @Mock BatteryService.HealthServiceWrapper.Callback mCallback;
-    @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier;
-    @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier;
-    BatteryService.HealthServiceWrapper mWrapper;
-
-    private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR;
-
-    @Override
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Override
-    public void tearDown() {
-        if (mWrapper != null)
-            mWrapper.getHandlerThread().quitSafely();
-    }
-
-    public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
-        return new ArgumentMatcher<T>() {
-            @Override public boolean matches(T e) {
-                return collection.contains(e);
-            }
-            @Override public String toString() {
-                return collection.toString();
-            }
-        };
-    }
-
-    private void initForInstances(String... instanceNamesArr) throws Exception {
-        final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
-        doAnswer((invocation) -> {
-                // technically, preexisting is ignored by
-                // BatteryService.HealthServiceWrapper.Notification, but still call it correctly.
-                sendNotification(invocation, true);
-                sendNotification(invocation, true);
-                sendNotification(invocation, false);
-                return null;
-            }).when(mMockedManager).registerForNotifications(
-                eq(IHealth.kInterfaceName),
-                argThat(isOneOf(instanceNames)),
-                any(IServiceNotification.class));
-
-        doReturn(mMockedManager).when(mManagerSupplier).get();
-        doReturn(mMockedHal)        // init calls this
-            .doReturn(mMockedHal)   // notification 1
-            .doReturn(mMockedHal)   // notification 2
-            .doReturn(mMockedHal2)  // notification 3
-            .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
-            .when(mHealthServiceSupplier).get(argThat(isOneOf(instanceNames)));
-
-        mWrapper = new BatteryService.HealthServiceWrapper();
-    }
-
-    private void waitHandlerThreadFinish() throws Exception {
-        for (int i = 0; i < 5; i++) {
-            if (!mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks()) {
-                return;
-            }
-            Thread.sleep(300);
-        }
-        assertFalse(mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks());
-    }
-
-    private static void sendNotification(InvocationOnMock invocation, boolean preexisting)
-            throws Exception {
-        ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
-                IHealth.kInterfaceName,
-                (String)invocation.getArguments()[1],
-                preexisting);
-    }
-
-    @SmallTest
-    public void testWrapPreferVendor() throws Exception {
-        initForInstances(VENDOR);
-        mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
-        waitHandlerThreadFinish();
-        verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
-        verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
-        verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(VENDOR));
-    }
-
-    @SmallTest
-    public void testNoService() throws Exception {
-        initForInstances("unrelated");
-        try {
-            mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
-            fail("Expect NoSuchElementException");
-        } catch (NoSuchElementException ex) {
-            // expected
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index b1da890..ae2b8dc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -42,7 +42,6 @@
 import android.graphics.drawable.Icon;
 import android.os.IBinder;
 import android.provider.Settings;
-import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
@@ -57,13 +56,16 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 /**
  * APCT tests for {@link AccessibilityManagerService}.
  */
-public class AccessibilityManagerServiceTest extends AndroidTestCase {
+public class AccessibilityManagerServiceTest {
     private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
     private static final int ACTION_ID = 20;
     private static final String LABEL = "label";
@@ -108,8 +110,8 @@
     private AccessibilityServiceConnection mAccessibilityServiceConnection;
     private AccessibilityManagerService mA11yms;
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
@@ -174,44 +176,48 @@
     }
 
     @SmallTest
+    @Test
     public void testRegisterSystemActionWithoutPermission() throws Exception {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
                 .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         try {
             mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
-            fail();
+            Assert.fail();
         } catch (SecurityException expected) {
         }
         verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
     }
 
     @SmallTest
+    @Test
     public void testRegisterSystemAction() throws Exception {
         mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
         verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
     }
 
-    @SmallTest
+    @Test
     public void testUnregisterSystemActionWithoutPermission() throws Exception {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
                 .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         try {
             mA11yms.unregisterSystemAction(ACTION_ID);
-            fail();
+            Assert.fail();
         } catch (SecurityException expected) {
         }
         verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
     }
 
     @SmallTest
+    @Test
     public void testUnregisterSystemAction() throws Exception {
         mA11yms.unregisterSystemAction(ACTION_ID);
         verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
     }
 
     @SmallTest
+    @Test
     public void testOnSystemActionsChanged() throws Exception {
         setupAccessibilityServiceConnection();
         mA11yms.notifySystemActionsChangedLocked(mUserState);
@@ -220,6 +226,7 @@
     }
 
     @SmallTest
+    @Test
     public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() {
         final AccessibilityUserState userState = mA11yms.mUserStates.get(
                 mA11yms.getCurrentUserIdLocked());
@@ -230,11 +237,12 @@
 
         mA11yms.onMagnificationTransitionEndedLocked(false);
 
-        assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+        Assert.assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
                 userState.getMagnificationModeLocked());
     }
 
     @SmallTest
+    @Test
     public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
         mUserState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME);
         mUserState.setMagnificationCapabilitiesLocked(
@@ -247,6 +255,7 @@
     }
 
     @SmallTest
+    @Test
     public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
         setupAccessibilityServiceConnection();
         when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java b/services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java
new file mode 100644
index 0000000..c97a67b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 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.health;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Mockito.*;
+
+import android.hardware.health.V2_0.IHealth;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+
+@RunWith(AndroidJUnit4.class)
+public class HealthServiceWrapperTest {
+
+    @Mock IServiceManager mMockedManager;
+    @Mock IHealth mMockedHal;
+    @Mock IHealth mMockedHal2;
+
+    @Mock HealthServiceWrapperHidl.Callback mCallback;
+    @Mock HealthServiceWrapperHidl.IServiceManagerSupplier mManagerSupplier;
+    @Mock HealthServiceWrapperHidl.IHealthSupplier mHealthServiceSupplier;
+    HealthServiceWrapper mWrapper;
+
+    private static final String VENDOR = HealthServiceWrapperHidl.INSTANCE_VENDOR;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @After
+    public void tearDown() {
+        if (mWrapper != null) mWrapper.getHandlerThread().quitSafely();
+    }
+
+    public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
+        return new ArgumentMatcher<T>() {
+            @Override
+            public boolean matches(T e) {
+                return collection.contains(e);
+            }
+
+            @Override
+            public String toString() {
+                return collection.toString();
+            }
+        };
+    }
+
+    private void initForInstances(String... instanceNamesArr) throws Exception {
+        final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
+        doAnswer(
+                (invocation) -> {
+                    // technically, preexisting is ignored by
+                    // HealthServiceWrapperHidl.Notification, but still call it correctly.
+                    sendNotification(invocation, true);
+                    sendNotification(invocation, true);
+                    sendNotification(invocation, false);
+                    return null;
+                })
+                .when(mMockedManager)
+                .registerForNotifications(
+                        eq(IHealth.kInterfaceName),
+                        argThat(isOneOf(instanceNames)),
+                        any(IServiceNotification.class));
+
+        doReturn(mMockedManager).when(mManagerSupplier).get();
+        doReturn(mMockedHal) // init calls this
+                .doReturn(mMockedHal) // notification 1
+                .doReturn(mMockedHal) // notification 2
+                .doReturn(mMockedHal2) // notification 3
+                .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
+                .when(mHealthServiceSupplier)
+                .get(argThat(isOneOf(instanceNames)));
+    }
+
+    private void waitHandlerThreadFinish() throws Exception {
+        for (int i = 0; i < 5; i++) {
+            if (!mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks()) {
+                return;
+            }
+            Thread.sleep(300);
+        }
+        assertFalse(mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks());
+    }
+
+    private static void sendNotification(InvocationOnMock invocation, boolean preexisting)
+            throws Exception {
+        ((IServiceNotification) invocation.getArguments()[2])
+                .onRegistration(
+                        IHealth.kInterfaceName, (String) invocation.getArguments()[1], preexisting);
+    }
+
+    private void createWrapper() throws RemoteException {
+        mWrapper = HealthServiceWrapper.create(mCallback, mManagerSupplier, mHealthServiceSupplier);
+    }
+
+    @SmallTest
+    @Test
+    public void testWrapPreferVendor() throws Exception {
+        initForInstances(VENDOR);
+        createWrapper();
+        waitHandlerThreadFinish();
+        verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
+        verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
+        verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(VENDOR));
+    }
+
+    @SmallTest
+    @Test
+    public void testNoService() throws Exception {
+        initForInstances("unrelated");
+        try {
+            createWrapper();
+            fail("Expect NoSuchElementException");
+        } catch (NoSuchElementException ex) {
+            // expected
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java
new file mode 100644
index 0000000..ddb9b43
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.notification;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.app.Application;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+import com.android.server.pm.PackageManagerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@RunWithLooper
+public class CountdownConditionProviderTest extends UiServiceTestCase {
+
+    CountdownConditionProvider mService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Intent startIntent =
+                new Intent("com.android.server.notification.CountdownConditionProvider");
+        startIntent.setPackage("android");
+        CountdownConditionProvider service = new CountdownConditionProvider();
+        service.attach(
+                getContext(),
+                null,               // ActivityThread not actually used in Service
+                CountdownConditionProvider.class.getName(),
+                null,               // token not needed when not talking with the activity manager
+                mock(Application.class),
+                null                // mocked services don't talk with the activity manager
+                );
+        service.onCreate();
+        service.onBind(startIntent);
+        mService = spy(service);
+   }
+
+    @Test
+    public void testGetPendingIntent() {
+        PendingIntent pi = mService.getPendingIntent(Uri.EMPTY);
+        assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
new file mode 100644
index 0000000..4c440ca
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.notification;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.app.Application;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+import com.android.server.pm.PackageManagerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@RunWithLooper
+public class EventConditionProviderTest extends UiServiceTestCase {
+
+    EventConditionProvider mService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Intent startIntent =
+                new Intent("com.android.server.notification.EventConditionProvider");
+        startIntent.setPackage("android");
+        EventConditionProvider service = new EventConditionProvider();
+        service.attach(
+                getContext(),
+                null,               // ActivityThread not actually used in Service
+                CountdownConditionProvider.class.getName(),
+                null,               // token not needed when not talking with the activity manager
+                mock(Application.class),
+                null                // mocked services don't talk with the activity manager
+                );
+        service.onCreate();
+        service.onBind(startIntent);
+        mService = spy(service);
+   }
+
+    @Test
+    public void testGetPendingIntent() {
+        PendingIntent pi = mService.getPendingIntent(1000);
+        assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index cbdf4fe..d89d64a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -254,7 +254,8 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
     }
@@ -276,7 +277,8 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
     }
@@ -301,7 +303,8 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
         clearInvocations(mAtm.mRootWindowContainer);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
@@ -337,8 +340,10 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
-        taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
+        taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
         clearInvocations(mAtm.mRootWindowContainer);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
@@ -391,7 +396,8 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
         clearInvocations(mAtm.mRootWindowContainer);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 42f4d58..6737b1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -20,12 +20,15 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.clearInvocations;
 
 import android.graphics.Rect;
+import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
 import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.MediumTest;
@@ -64,6 +67,7 @@
         mTaskFragment = new TaskFragmentBuilder(mAtm)
                 .setCreateParentTask()
                 .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
                 .build();
         mLeash = mTaskFragment.getSurfaceControl();
         spyOn(mTaskFragment);
@@ -103,4 +107,23 @@
         verify(mTransaction).setPosition(mLeash, 500, 500);
         verify(mTransaction).setWindowCrop(mLeash, 500, 500);
     }
+
+    /**
+     * Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an
+     * activity that has not yet been attached to a process because it is being initialized but
+     * belongs to the TaskFragmentOrganizer process is still reported in the TaskFragmentInfo.
+     */
+    @Test
+    public void testActivityStillReported_NotYetAssignedToProcess() {
+        mTaskFragment.addChild(new ActivityBuilder(mAtm).setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID)
+                .setProcessName(DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME).build());
+        final ActivityRecord activity = mTaskFragment.getTopMostActivity();
+        // Remove the process to simulate an activity that has not yet been attached to a process
+        activity.app = null;
+        final TaskFragmentInfo info = activity.getTaskFragment().getTaskFragmentInfo();
+        assertEquals(1, info.getRunningActivityCount());
+        assertEquals(1, info.getActivities().size());
+        assertEquals(false, info.isEmpty());
+        assertEquals(activity.token, info.getActivities().get(0));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 386ff4b..996b4b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -126,6 +126,9 @@
     // Default package name
     static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
 
+    static final int DEFAULT_TASK_FRAGMENT_ORGANIZER_UID = 10000;
+    static final String DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME = "Test:TaskFragmentOrganizer";
+
     // Default base activity name
     private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
 
@@ -1234,7 +1237,8 @@
             }
             if (mOrganizer != null) {
                 taskFragment.setTaskFragmentOrganizer(
-                        mOrganizer.getOrganizerToken(), 10000 /* pid */);
+                        mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID,
+                        DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
             }
             return taskFragment;
         }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2141e42..3e9ae38 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressAutoDoc;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -5978,12 +5979,15 @@
      * any carrier specific configuration has been applied.
      *
      * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app
+     * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}).
      *
      * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
      * @return A {@link PersistableBundle} containing the config for the given subId, or default
      *         values for an invalid subId.
      */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     @Nullable
     public PersistableBundle getConfigForSubId(int subId) {
         try {
@@ -6072,10 +6076,13 @@
      * called to confirm whether any carrier specific configuration has been applied.
      *
      * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app
+     * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}).
      *
      * @see #getConfigForSubId
      */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     @Nullable
     public PersistableBundle getConfig() {
         return getConfigForSubId(SubscriptionManager.getDefaultSubscriptionId());
@@ -6084,8 +6091,8 @@
     /**
      * Determines whether a configuration {@link PersistableBundle} obtained from
      * {@link #getConfig()} or {@link #getConfigForSubId(int)} corresponds to an identified carrier.
-     * <p>
-     * When an app receives the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED}
+     *
+     * <p>When an app receives the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED}
      * broadcast which informs it that the carrier configuration has changed, it is possible
      * that another reload of the carrier configuration has begun since the intent was sent.
      * In this case, the carrier configuration the app fetches (e.g. via {@link #getConfig()})
@@ -6094,14 +6101,12 @@
      * return true because it may belong to another previous identified carrier. Users should
      * always call {@link #getConfig()} or {@link #getConfigForSubId(int)} after receiving the
      * broadcast {@link #ACTION_CARRIER_CONFIG_CHANGED}.
-     * </p>
-     * <p>
-     * After using {@link #getConfig()} or {@link #getConfigForSubId(int)} an app should always
+     *
+     * <p>After using {@link #getConfig()} or {@link #getConfigForSubId(int)} an app should always
      * use this method to confirm whether any carrier specific configuration has been applied.
      * Especially when an app misses the broadcast {@link #ACTION_CARRIER_CONFIG_CHANGED} but it
      * still needs to get the current configuration, it must use this method to verify whether the
      * configuration is default or carrier overridden.
-     * </p>
      *
      * @param bundle the configuration bundle to be checked.
      * @return boolean true if any carrier specific configuration bundle has been applied, false
@@ -6113,19 +6118,20 @@
 
     /**
      * Calling this method triggers telephony services to fetch the current carrier configuration.
-     * <p>
-     * Normally this does not need to be called because the platform reloads config on its own.
+     *
+     * <p>Normally this does not need to be called because the platform reloads config on its own.
      * This should be called by a carrier service app if it wants to update config at an arbitrary
      * moment.
-     * </p>
-     * <p>Requires that the calling app has carrier privileges.
-     * <p>
-     * This method returns before the reload has completed, and
-     * {@link android.service.carrier.CarrierService#onLoadConfig} will be called from an
-     * arbitrary thread.
-     * </p>
-     * @see TelephonyManager#hasCarrierPrivileges
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or the calling app
+     * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}).
+     *
+     * <p>This method returns before the reload has completed, and {@link
+     * android.service.carrier.CarrierService#onLoadConfig} will be called from an arbitrary thread.
      */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void notifyConfigChangedForSubId(int subId) {
         try {
             ICarrierConfigLoader loader = getICarrierConfigLoader();
@@ -6141,11 +6147,10 @@
     }
 
     /**
-     * Request the carrier config loader to update the cofig for phoneId.
-     * <p>
-     * Depending on simState, the config may be cleared or loaded from config app. This is only used
-     * by SubscriptionInfoUpdater.
-     * </p>
+     * Request the carrier config loader to update the config for phoneId.
+     *
+     * <p>Depending on simState, the config may be cleared or loaded from config app. This is only
+     * used by SubscriptionInfoUpdater.
      *
      * @hide
      */
@@ -6216,13 +6221,16 @@
      * Gets the configuration values for a component using its prefix.
      *
      * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app
+     * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}).
      *
      * @param prefix prefix of the component.
      * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
      *
      * @see #getConfigForSubId
      */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     @Nullable
     public PersistableBundle getConfigByComponentForSubId(@NonNull String prefix, int subId) {
         PersistableBundle configs = getConfigForSubId(subId);
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 61c269c..866fd2c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -529,7 +529,7 @@
     int RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP = 223;
     int RIL_REQUEST_GET_SLICING_CONFIG = 224;
     int RIL_REQUEST_ENABLE_VONR = 225;
-    int RIL_REQUEST_IS_VONR_ENABLED = 225;
+    int RIL_REQUEST_IS_VONR_ENABLED = 226;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/FlickerTests/OWNERS b/tests/FlickerTests/OWNERS
index b556101..c1221e3 100644
--- a/tests/FlickerTests/OWNERS
+++ b/tests/FlickerTests/OWNERS
@@ -1,3 +1,4 @@
 # Bug component: 909476
 include /services/core/java/com/android/server/wm/OWNERS
-natanieljr@google.com
\ No newline at end of file
+natanieljr@google.com
+pablogamito@google.com
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 9f26c31..209d1aa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -65,9 +65,9 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
 class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
                 device.pressBack()
                 wmHelper.waitForHomeActivityVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 795766f..ac557cf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -64,9 +64,9 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
 class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             transitions {
                 device.pressHome()
                 wmHelper.waitForHomeActivityVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 339a5bd..8a2ddf1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -32,7 +32,6 @@
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -54,11 +53,11 @@
     /**
      * Specification of the test transition to execute
      */
-    protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = {
+    protected open val transition: FlickerBuilder.() -> Unit = {
         setup {
             eachRun {
                 testApp.launchViaIntent(wmHelper)
-                this.setRotation(testSpec.config.startRotation)
+                this.setRotation(testSpec.startRotation)
             }
         }
         teardown {
@@ -75,7 +74,7 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            transition(testSpec.config)
+            transition()
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 5e21aff..c7dfbf8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -33,7 +33,6 @@
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -63,7 +62,7 @@
 @Group2
 class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 0582685..5315da1d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -34,7 +34,6 @@
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -64,7 +63,7 @@
 @Group2
 class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 91b3d3d..d063b69 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -119,14 +119,14 @@
     @Presubmit
     @Test
     fun navBarLayerRotatesAndScales() {
-        Assume.assumeFalse(testSpec.isRotated)
+        Assume.assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
         testSpec.navBarLayerRotatesAndScales()
     }
 
     @FlakyTest
     @Test
     fun navBarLayerRotatesAndScales_Flaky() {
-        Assume.assumeTrue(testSpec.isRotated)
+        Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
         testSpec.navBarLayerRotatesAndScales()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index a9568b3..005c4f5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -29,7 +29,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.traces.common.FlickerComponentName
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -71,14 +70,14 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
             setup {
                 eachRun {
-                    this.setRotation(testSpec.config.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             teardown {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index f6febe9e..5d8a382 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -37,7 +37,6 @@
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -60,7 +59,7 @@
 @Group2
 class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
     private val isShellTransitionsEnabled =
             SystemProperties.getBoolean("persist.debug.shell_transit", false)
 
@@ -76,7 +75,7 @@
                     device.pressRecentApps()
                     wmHelper.waitImeGone()
                     wmHelper.waitForAppTransitionIdle()
-                    this.setRotation(testSpec.config.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             transitions {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 4c506b0..56ec80c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -33,7 +33,6 @@
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 
@@ -56,14 +55,14 @@
 class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = SimpleAppHelper(instrumentation)
-    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
             setup {
                 eachRun {
-                    this.setRotation(testSpec.config.startRotation)
+                    this.setRotation(testSpec.startRotation)
                     testApp.launchViaIntent(wmHelper)
                     wmHelper.waitForFullScreenApp(testApp.component)
                     wmHelper.waitForAppTransitionIdle()
@@ -86,7 +85,7 @@
             transitions {
                 // [Step1]: Swipe right from imeTestApp to testApp task
                 createTag(TAG_IME_VISIBLE)
-                val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+                val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
                 device.swipe(0, displayBounds.bounds.height(),
                         displayBounds.bounds.width(), displayBounds.bounds.height(), 50)
 
@@ -96,7 +95,7 @@
             }
             transitions {
                 // [Step2]: Swipe left to back to imeTestApp task
-                val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+                val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
                 device.swipe(displayBounds.bounds.width(), displayBounds.bounds.height(),
                         0, displayBounds.bounds.height(), 50)
                 wmHelper.waitForFullScreenApp(imeTestApp.component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index f74a771..648353e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -26,7 +26,6 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.LAUNCHER_COMPONENT
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
@@ -60,7 +59,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
 class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) {
-    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
 
     /**
@@ -70,8 +69,6 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            withTestName { testSpec.name }
-            repeat { testSpec.config.repetitions }
             setup {
                 eachRun {
                     testApp.launchViaIntent(wmHelper)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 2f5a389..c4fec7f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import org.junit.FixMethodOrder
@@ -59,13 +58,13 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             setup {
                 eachRun {
                     removeAllTasksButHome()
-                    this.setRotation(testSpec.config.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             teardown {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index a05b78c..c572e8b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import org.junit.FixMethodOrder
@@ -64,9 +63,9 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             setup {
                 test {
                     testApp.launchViaIntent(wmHelper)
@@ -81,7 +80,7 @@
                         WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT),
                         WindowManagerConditionsFactory.hasLayersAnimating().negate()
                     )
-                    this.setRotation(testSpec.config.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             transitions {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index ad0da5b..2c84305 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -66,9 +66,9 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-        get() = { args ->
-            super.transition(this, args)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
             setup {
                 eachRun {
                     device.sleep()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 7af7b3a..b104b97 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -31,9 +31,7 @@
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.replacesLayer
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -50,13 +48,11 @@
     /**
      * Defines the transition used to run the test
      */
-    protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = {
-        withTestName { testSpec.name }
-        repeat { testSpec.config.repetitions }
+    protected open val transition: FlickerBuilder.() -> Unit = {
         setup {
             test {
                 device.wakeUpAndGoToHomeScreen()
-                this.setRotation(testSpec.config.startRotation)
+                this.setRotation(testSpec.startRotation)
             }
         }
         teardown {
@@ -73,7 +69,7 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            transition(testSpec.config)
+            transition()
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 5edee0c..dc7df34 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -59,9 +58,9 @@
     /**
      * Defines the transition used to run the test
      */
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             setup {
                 test {
                     testApp.launchViaIntent(wmHelper)
@@ -69,7 +68,7 @@
                 eachRun {
                     device.pressHome()
                     wmHelper.waitForHomeActivityVisible()
-                    this.setRotation(testSpec.config.startRotation)
+                    this.setRotation(testSpec.startRotation)
                 }
             }
             teardown {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 495e2d6..769cb1a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -33,8 +33,6 @@
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME
@@ -71,8 +69,6 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            withTestName { testSpec.name }
-            repeat { testSpec.config.repetitions }
             setup {
                 eachRun {
                     mTestApp.launchViaIntent(wmHelper)
@@ -149,7 +145,7 @@
     @Test
     fun colorLayerIsVisibleDuringTransition() {
         val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer")
-        val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+        val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
         testSpec.assertLayers {
             this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 52904cc..5b0372d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -32,11 +32,9 @@
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isRotated
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
@@ -68,7 +66,7 @@
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
-    private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+    private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
@@ -92,7 +90,7 @@
                         startDisplayBounds.bounds.bottom,
                         2 * startDisplayBounds.bounds.right / 3,
                         startDisplayBounds.bounds.bottom,
-                        if (testSpec.config.startRotation.isRotated()) 75 else 30
+                        if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30
                 )
 
                 wmHelper.waitForFullScreenApp(testApp1.component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 842aa2b..99bc115 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -32,12 +32,9 @@
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isRotated
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
@@ -69,13 +66,11 @@
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
-    private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+    private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            withTestName { testSpec.name }
-            repeat { testSpec.config.repetitions }
             setup {
                 eachRun {
                     testApp1.launchViaIntent(wmHelper)
@@ -93,7 +88,7 @@
                             startDisplayBounds.bounds.bottom,
                             2 * startDisplayBounds.bounds.right / 3,
                             startDisplayBounds.bounds.bottom,
-                            if (testSpec.config.startRotation.isRotated()) 75 else 30
+                            if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30
                     )
 
                     wmHelper.waitForFullScreenApp(testApp1.component)
@@ -110,7 +105,7 @@
                         startDisplayBounds.bounds.bottom,
                         startDisplayBounds.bounds.right / 3,
                         startDisplayBounds.bounds.bottom,
-                        if (testSpec.config.startRotation.isRotated()) 75 else 30
+                        if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30
                 )
 
                 wmHelper.waitForFullScreenApp(testApp2.component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 10ca0d9..dcb5c86 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -35,7 +35,6 @@
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
@@ -64,7 +63,7 @@
 class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = SimpleAppHelper(instrumentation)
-    private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+    private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 0bde8a0..eb7d29d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -85,9 +85,9 @@
     val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
 
     override val testApp = SimpleAppHelper(instrumentation)
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             setup {
                 test {
                     testApp.launchViaIntent(wmHelper)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index e850632..ce2347d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -22,14 +22,12 @@
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.traces.common.FlickerComponentName
 import org.junit.Test
 
@@ -41,10 +39,10 @@
 
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
 
-    protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = {
+    protected open val transition: FlickerBuilder.() -> Unit = {
         setup {
             eachRun {
-                this.setRotation(testSpec.config.startRotation)
+                this.setRotation(testSpec.startRotation)
             }
         }
         teardown {
@@ -53,7 +51,7 @@
             }
         }
         transitions {
-            this.setRotation(testSpec.config.endRotation)
+            this.setRotation(testSpec.endRotation)
         }
     }
 
@@ -64,7 +62,7 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            transition(testSpec.config)
+            transition()
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 310f04b..c55d7af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -81,14 +81,14 @@
 ) : RotationTransition(testSpec) {
     override val testApp = SeamlessRotationAppHelper(instrumentation)
 
-    override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+    override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this, it)
+            super.transition(this)
             setup {
                 test {
                     testApp.launchViaIntent(wmHelper,
-                        stringExtras = mapOf(
-                            ActivityOptions.EXTRA_STARVE_UI_THREAD to it.starveUiThread.toString())
+                        stringExtras = mapOf(ActivityOptions.EXTRA_STARVE_UI_THREAD
+                            to testSpec.starveUiThread.toString())
                     )
                 }
             }
@@ -185,15 +185,17 @@
     override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
 
     companion object {
-        private val Map<String, Any?>.starveUiThread
-            get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean
+        private val FlickerTestParameter.starveUiThread
+            get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean
 
-        private fun FlickerTestParameter.createConfig(
+        private fun createConfig(
+            sourceConfig: FlickerTestParameter,
             starveUiThread: Boolean
-        ): MutableMap<String, Any?> {
-            val config = this.config.toMutableMap()
-            config[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread
-            return config
+        ): FlickerTestParameter {
+            val newConfig = sourceConfig.config.toMutableMap()
+                .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread }
+            val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
+            return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt")
         }
 
         /**
@@ -206,15 +208,10 @@
         private fun getConfigurations(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigRotationTests(repetitions = 2)
-                .flatMap {
-                    val defaultRun = it.createConfig(starveUiThread = false)
-                    val busyUiRun = it.createConfig(starveUiThread = true)
-                    listOf(
-                        FlickerTestParameter(defaultRun),
-                        FlickerTestParameter(busyUiRun,
-                            name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD"
-                        )
-                    )
+                .flatMap { sourceConfig ->
+                    val defaultRun = createConfig(sourceConfig, starveUiThread = false)
+                    val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
+                    listOf(defaultRun, busyUiRun)
                 }
         }
 
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 5253c3e..2b0037e 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vcn;
 
+import static android.net.IpSecManager.IpSecTunnelInterface;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
@@ -24,6 +25,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
 
@@ -36,8 +39,11 @@
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.net.IpSecManager;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -59,6 +65,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.net.InetAddress;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -71,6 +79,8 @@
 public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
     private static final int TEST_UID = Process.myUid() + 1;
 
+    private static final String LOOPBACK_IFACE = "lo";
+
     private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
     private static final int TEST_SIM_SLOT_INDEX = 1;
     private static final int TEST_SUBSCRIPTION_ID_1 = 2;
@@ -78,6 +88,12 @@
     private static final int TEST_SUBSCRIPTION_ID_2 = 3;
     private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class);
     private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP;
+    private static final String TEST_TCP_BUFFER_SIZES = "1,2,3,4,5,6";
+    private static final int TEST_MTU = 1300;
+    private static final int TEST_MTU_DELTA = 64;
+    private static final List<LinkAddress> TEST_INTERNAL_ADDRESSES =
+            Arrays.asList(new LinkAddress(DUMMY_ADDR, 16));
+    private static final List<InetAddress> TEST_DNS_ADDRESSES = Arrays.asList(DUMMY_ADDR);
 
     private static final int TEST_UPSTREAM_BANDWIDTH = 1234;
     private static final int TEST_DOWNSTREAM_BANDWIDTH = 2345;
@@ -169,6 +185,59 @@
     }
 
     @Test
+    public void testBuildLinkProperties() throws Exception {
+        final IpSecTunnelInterface tunnelIface =
+                mContext.getSystemService(IpSecManager.class)
+                        .createIpSecTunnelInterface(
+                                DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
+
+        final LinkProperties underlyingLp = new LinkProperties();
+        underlyingLp.setInterfaceName(LOOPBACK_IFACE);
+        underlyingLp.setTcpBufferSizes(TEST_TCP_BUFFER_SIZES);
+        doReturn(TEST_MTU).when(mDeps).getUnderlyingIfaceMtu(LOOPBACK_IFACE);
+
+        final VcnChildSessionConfiguration childSessionConfig =
+                mock(VcnChildSessionConfiguration.class);
+        doReturn(TEST_INTERNAL_ADDRESSES).when(childSessionConfig).getInternalAddresses();
+        doReturn(TEST_DNS_ADDRESSES).when(childSessionConfig).getInternalDnsServers();
+
+        UnderlyingNetworkRecord record =
+                new UnderlyingNetworkRecord(
+                        mock(Network.class, CALLS_REAL_METHODS),
+                        new NetworkCapabilities.Builder().build(),
+                        underlyingLp,
+                        false);
+
+        final LinkProperties vcnLp1 =
+                mGatewayConnection.buildConnectedLinkProperties(
+                        VcnGatewayConnectionConfigTest.buildTestConfig(),
+                        tunnelIface,
+                        childSessionConfig,
+                        record);
+
+        verify(mDeps).getUnderlyingIfaceMtu(LOOPBACK_IFACE);
+
+        // Instead of having to recalculate the final MTU (after accounting for IPsec overhead),
+        // calculate another set of Link Properties with a lower MTU, and calculate the delta.
+        doReturn(TEST_MTU - TEST_MTU_DELTA).when(mDeps).getUnderlyingIfaceMtu(LOOPBACK_IFACE);
+
+        final LinkProperties vcnLp2 =
+                mGatewayConnection.buildConnectedLinkProperties(
+                        VcnGatewayConnectionConfigTest.buildTestConfig(),
+                        tunnelIface,
+                        childSessionConfig,
+                        record);
+
+        verify(mDeps, times(2)).getUnderlyingIfaceMtu(LOOPBACK_IFACE);
+
+        assertEquals(tunnelIface.getInterfaceName(), vcnLp1.getInterfaceName());
+        assertEquals(TEST_INTERNAL_ADDRESSES, vcnLp1.getLinkAddresses());
+        assertEquals(TEST_DNS_ADDRESSES, vcnLp1.getDnsServers());
+        assertEquals(TEST_TCP_BUFFER_SIZES, vcnLp1.getTcpBufferSizes());
+        assertEquals(TEST_MTU_DELTA, vcnLp1.getMtu() - vcnLp2.getMtu());
+    }
+
+    @Test
     public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
         verifyWakeLockSetUp();
 
diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS
index 69dfcc9..4f655e5 100644
--- a/tools/aapt2/OWNERS
+++ b/tools/aapt2/OWNERS
@@ -1,4 +1,4 @@
 set noparent
 toddke@google.com
-rtmitchell@google.com
+zyy@google.com
 patb@google.com
\ No newline at end of file